Xero-OpenAPI icon indicating copy to clipboard operation
Xero-OpenAPI copied to clipboard

Xero API incorrectly updating discount fields for invoice when updating the SentToContact field

Open JoeCMorgan opened this issue 9 months ago • 3 comments

A quick summary and/or background As mentioned in the title, when I'm hitting the Invoices endpoint to update an invoice, through either Postman, the API explorer, or the Python SDK (updateInvoice endpoint), the discount fields are being modified, even though I'm only marking the invoice as being sent.

Steps to reproduce This is tricky as my invoices are being create via the Parex bridge for Shopify. However, the invoice is created with a discount, as shown below:

{
    "Id": "e91927c5-546a-40c0-8655-5daf048ed0b5",
    "Status": "OK",
    "ProviderName": "Send Invoice Emails",
    "DateTimeUTC": "/Date(1744023335310)/",
    "Invoices": [
        {
            "Type": "ACCREC",
            "InvoiceID": "21fbe4ca-8e63-45fa-a513-431976c648a7",
            "InvoiceNumber": "SHP_#1347",
            "Reference": "pos",
            "Prepayments": [],
            "Overpayments": [],
            "AmountDue": 45.55,
            "AmountPaid": 0.00,
            "SentToContact": false,
            "CurrencyRate": 1.0000000000,
            "TotalDiscount": 2.39,
            "IsDiscounted": true,
            "HasAttachments": false,
            "HasErrors": false,
            "Attachments": [],
            "InvoicePaymentServices": [],
            "Contact": {...},
            "DateString": "2025-04-07T00:00:00",
            "Date": "/Date(1743984000000+0000)/",
            "DueDateString": "2025-05-30T00:00:00",
            "DueDate": "/Date(1748563200000+0000)/",
            "BrandingThemeID": "d45ca3b8-18e1-4cca-9e79-d1f50a790575",
            "Status": "AUTHORISED",
            "LineAmountTypes": "Inclusive",
            "LineItems": [
                {
                    "ItemCode": "SOL-STH-MID-MC070-1L",
                    "Description": "Premium Standard Thinner, 1L",
                    "UnitAmount": 1.92,
                    "TaxType": "OUTPUT2",
                    "TaxAmount": 5.78,
                    "LineAmount": 34.66,
                    "AccountCode": "200",
                    "Item": {
                        "ItemID": "f688ec16-99dc-4efd-99ca-e97c56a7ba3b",
                        "Name": "Premium Standard Thinner, 1L",
                        "Code": "SOL-STH-MID-MC070-1L"
                    },
                    "Tracking": [],
                    "Quantity": 19.0000,
                    "DiscountRate": 4.99,
                    "LineItemID": "c522edc5-e632-4e0a-9ab0-147642cdd7c0",
                    "AccountID": "e15ef38f-0423-4883-82b0-fba43e00ceba",
                    "DiscountAmount": 1.8200,
                    "ValidationErrors": []
                },
                {
                    "ItemCode": "SOL-STH-MID-MC070-1L",
                    "Description": "Premium Standard Thinner, 1L",
                    "UnitAmount": 1.91,
                    "TaxType": "OUTPUT2",
                    "TaxAmount": 1.81,
                    "LineAmount": 10.89,
                    "AccountCode": "200",
                    "Item": {
                        "ItemID": "f688ec16-99dc-4efd-99ca-e97c56a7ba3b",
                        "Name": "Premium Standard Thinner, 1L",
                        "Code": "SOL-STH-MID-MC070-1L"
                    },
                    "Tracking": [],
                    "Quantity": 6.0000,
                    "DiscountRate": 4.97,
                    "LineItemID": "7520ff63-791b-41f1-bea7-94b34c062915",
                    "AccountID": "e15ef38f-0423-4883-82b0-fba43e00ceba",
                    "DiscountAmount": 0.5700,
                    "ValidationErrors": []
                }
            ],
            "SubTotal": 37.96,
            "TotalTax": 7.59,
            "Total": 45.55,
            "UpdatedDateUTC": "/Date(1744023317490+0000)/",
            "CurrencyCode": "GBP"
        }
    ]
}

The discount fields above match what is entered into Shopify, therefore the sync is correct.

However, when I attempt the mark the invoice as paid in postman, the discount values change. I'm hitting the Invoices/GUID endpoint, with the following payload:

{
    "InvoiceID": "21fbe4ca-8e63-45fa-a513-431976c648a7",
    "SentToContact": "true"
}

I get the following response:

{
    "Id": "d24a32b4-d84c-444f-9b6f-7d6078f68473",
    "Status": "OK",
    "ProviderName": "Send Invoice Emails",
    "DateTimeUTC": "/Date(1744023257300)/",
    "Invoices": [
        {
            "Type": "ACCREC",
            "InvoiceID": "21fbe4ca-8e63-45fa-a513-431976c648a7",
            "InvoiceNumber": "SHP_#1347",
            "Reference": "pos",
            "Prepayments": [],
            "Overpayments": [],
            "AmountDue": 45.55,
            "AmountPaid": 0.00,
            "SentToContact": true,
            "CurrencyRate": 1.0000000000,
            "TotalDiscount": 9.96,
            "IsDiscounted": true,
            "HasErrors": false,
            "InvoicePaymentServices": [],
            "Contact": {...},
            "DateString": "2025-04-07T00:00:00",
            "Date": "/Date(1743984000000+0000)/",
            "DueDateString": "2025-05-30T00:00:00",
            "DueDate": "/Date(1748563200000+0000)/",
            "BrandingThemeID": "d45ca3b8-18e1-4cca-9e79-d1f50a790575",
            "Status": "AUTHORISED",
            "LineAmountTypes": "Inclusive",
            "LineItems": [
                {
                    "ItemCode": "SOL-STH-MID-MC070-1L",
                    "Description": "Premium Standard Thinner, 1L",
                    "UnitAmount": 1.92,
                    "TaxType": "OUTPUT2",
                    "TaxAmount": 5.78,
                    "LineAmount": 34.66,
                    "AccountCode": "200",
                    "Item": {
                        "ItemID": "f688ec16-99dc-4efd-99ca-e97c56a7ba3b",
                        "Name": "Premium Standard Thinner, 1L",
                        "Code": "SOL-STH-MID-MC070-1L"
                    },
                    "Tracking": [],
                    "Quantity": 19.0000,
                    "DiscountRate": 13.68,
                    "LineItemID": "c522edc5-e632-4e0a-9ab0-147642cdd7c0",
                    "AccountID": "e15ef38f-0423-4883-82b0-fba43e00ceba",
                    "DiscountEnteredAsPercent": false,
                    "ValidationErrors": []
                },
                {
                    "ItemCode": "SOL-STH-MID-MC070-1L",
                    "Description": "Premium Standard Thinner, 1L",
                    "UnitAmount": 1.91,
                    "TaxType": "OUTPUT2",
                    "TaxAmount": 1.81,
                    "LineAmount": 10.89,
                    "AccountCode": "200",
                    "Item": {
                        "ItemID": "f688ec16-99dc-4efd-99ca-e97c56a7ba3b",
                        "Name": "Premium Standard Thinner, 1L",
                        "Code": "SOL-STH-MID-MC070-1L"
                    },
                    "Tracking": [],
                    "Quantity": 6.0000,
                    "DiscountRate": 43.37,
                    "LineItemID": "7520ff63-791b-41f1-bea7-94b34c062915",
                    "AccountID": "e15ef38f-0423-4883-82b0-fba43e00ceba",
                    "DiscountEnteredAsPercent": false,
                    "ValidationErrors": []
                }
            ],
            "SubTotal": 37.96,
            "TotalTax": 7.59,
            "Total": 45.55,
            "UpdatedDateUTC": "/Date(1744023257207+0000)/",
            "CurrencyCode": "GBP"
        }
    ]
}

Notice how the DiscountRate has changed, and the DiscountAmount has disappeared? Why is this happening when all I'm updating is the SentToContact field?

What you expected would happen I would expect only the SentToContact field for that specific invoice to be modified, and no other fields.

What actually happens Shown above.

Notes As mentioned, I first noticed that something similar was happening in the Python SDK - an error message was returned saying Discount must be between 0.00 and 100.00 or empty.. So I tested it in the API explorer, and postman, and I get the above results. Therefore I believe this is an issue with the API.

JoeCMorgan avatar Apr 08 '25 10:04 JoeCMorgan