Search code examples
c#quickbooks-onlineintuit-partner-platform

Quickbooks Online API - Payment 'Unapplied' when Invoice ID provided


I've managed to create a payment using the C# .NET SDK, however it keeps showing up as 'unapplied' when I check in QBO.

I am providing the Invoice ID and tried to follow their developer API documentation but I been at this so long now that maybe I am missing something?

The following code creates the payment but doesn't 'receive' the payment towards the invoice, is there something I missed or need to do in order for the two to be linked together?

Payment payment = new Payment
{
    ProcessPayment = false,
    CustomerRef = new ReferenceType { name = customer.DisplayName, Value = customer.Id },
    CurrencyRef = new ReferenceType { type = "Currency", Value = "CAD" },
    TotalAmt = amount,
    TotalAmtSpecified = true
};

if (method == PaymentTypes.Cash)
{
    var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "1");
    if (paymentType != null)
    {
        payment.PaymentMethodRef = new ReferenceType()
            {name = paymentType.Name, Value = paymentType.Id};
    }
}

if (method == PaymentTypes.DebitCard)
{
    var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "9");
    if (paymentType != null)
    {
        payment.PaymentMethodRef = new ReferenceType()
            { name = paymentType.Name, Value = paymentType.Id };
    }
}

if (method == PaymentTypes.CreditCard)
{
    var paymentType = paymentMethods.FirstOrDefault(o => o.Id == "8");
    if (paymentType != null)
    {
        payment.PaymentMethodRef = new ReferenceType()
            { name = paymentType.Name, Value = paymentType.Id };
    }
}

List<LinkedTxn> linkedTxns = new List<LinkedTxn>
{
    new LinkedTxn()
    {
        TxnId = invoice.Id,
        TxnType = TxnTypeEnum.Invoice.ToString()
    },
};

foreach (Line line in invoice.Line)
{
    //line.Amount = amount;
    //line.AmountSpecified = true;
    line.Received = amount;
    line.ReceivedSpecified = true;
    line.DetailType = LineDetailTypeEnum.PaymentLineDetail;
    line.DetailTypeSpecified = true;
    line.LinkedTxn = linkedTxns.ToArray();
}

payment.DepositToAccountRef = new ReferenceType() { Value = "5" };
payment.Line = invoice.Line;
payment.PaymentRefNum = reference;

DataService dataService = new DataService(serviceContext);
dataService.Add<Payment>(payment);

enter image description here


Solution

  • This is not an answer. However there's too much to add to the comments. I'm hoping this will be a helpful starting point (if not I'll remove it later).

    Firstly I'd suggest refactoring your code and parameterise your variables. Doing so you should be able to preform repeatable testing in isolation.

    When you preform the dataService.Add<Payment>(payment), store the response object as it may offer clues on how the request was processed. Alternatively if this is suppressing error messages, you might want to try an use Postman to send HTTP requests. This may help determine what's parameters are missing/ incorrect.

    Avoid creating objects that are partially assigned it makes it a lot easier to read 7 work out which properties need to be assigned.

    Also if you have a look at Full update a payment section on https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/payment the json payload has an additional Line with the property TxnType set to CreditMemo. I would assume you'll need to add something like ReceivePayment or CreditCardPayment?

    To refactor your code consider:

    // TODO - Set variables for testing purposes. 
    // This can be added as params to a method.
    decimal amount = 100;
    string reference = "";
    string invoiceId = ""; // invoice.Id
    string customerDisplayName = ""; //customer.DisplayName
    string customerId = ""; //customer.Id
    string paymentName = "Cash"; // paymentType.Name
    string paymentId = "1"; // paymentType.Id
    List<Line> lines = new List<Line>(); // invoice.Line
    
                            
    if(lines.Count() == 0)
    {
        // TODO: You might want to check there are lines?
        throw new ArgumentException("No invoice.");
    }
    
    Line[] invoiceLines = lines.Select(m => new Line()
    {
        AnyIntuitObject = m.AnyIntuitObject,
        Amount = m.Amount,
        AmountSpecified = m.AmountSpecified,
        CustomField = m.CustomField,
        Id = m.Id,
        LineNum = m.LineNum,
        Description = m.Description,
        DetailType = LineDetailTypeEnum.PaymentLineDetail, //m.DetailType,
        DetailTypeSpecified = true, //m.DetailTypeSpecified,
        LinkedTxn = new List<LinkedTxn>
        {
            new LinkedTxn()
            {
                TxnId = invoiceId,
                TxnType = TxnTypeEnum.Invoice.ToString() // TODO: Should this be ReceivePayment?
            },
        }.ToArray(), //m.LinkedTxn,
        LineEx = m.LineEx,
        Received = amount, //m.Received,    
        ReceivedSpecified = true // m.ReceivedSpecified
    }).ToArray();            
    
    Payment payment = new Payment
    {
        ProcessPayment = false,
        CustomerRef = new ReferenceType { name = customerDisplayName, Value = customerId },
        CurrencyRef = new ReferenceType { type = "Currency", Value = "CAD" },
        TotalAmt = amount,
        TotalAmtSpecified = true,
        DepositToAccountRef = new ReferenceType() { Value = "5" },
        Line = invoiceLines, // Variable is for debugging purposes - it should be inline or call a method. 
        PaymentRefNum = reference,
        PaymentMethodRef = new ReferenceType()
        {
            name = paymentName,
            Value = paymentId,
        }
    };
    
    DataService dataService = new DataService(serviceContext);
    Payment results = dataService.Add<Payment>(payment);
    
    var json = JsonConvert.SerializeObject(results);
    Console.Write(json); // TODO - Use break point/ or write response somewhere for clues.