Search code examples
reactjspaypalpaypal-sandboxpaypal-rest-sdk

PayPal responding with "CANNOT_MIX_CURRENCIES" when purchase unit currency changed


I'm selling an item which has a different price and currency depending on the delivery address.

If the user has two addresses, one in the US and another in the UK, they will be able to change the delivery address through PayPal. When the user changes the region from US to UK, PayPal informs my site that there has been a region change and it will reply with the new currency and price, changing from 345$ to £305.

When PayPal receives this update, PayPal displays an error to the customer. In the network tab there is more insight, and one can find the following error: CANNOT_MIX_CURRENCIES. However, both item prices and total prices are all converted to the desired currency, so there is no "mix" of currencies. Below is the request and the accompanying response that generates the error.

Request: PATCH -> www.sandbox.paypal.com/smart/api/order/890595684S747592L/patch

{
    "data":{
        "patch":[
            {
                "op":"replace",
                "path":"/purchase_units/@reference_id=='xxx'",
                "value":{
                    "reference_id":"xxx",
                    "invoice_id":"xxx",
                    "custom_id":1,
                    "description":"xxx",
                    "amount":{
                        "currency_code":"GBP",
                        "value":"305.00",
                        "breakdown":{
                            "item_total":{
                                "currency_code":"GBP",
                                "value":"305.00"
                            },
                            "shipping":{
                                "currency_code":"GBP",
                                "value":"0.00"
                            },
                            "tax_total":{
                                "currency_code":"GBP",
                                "value":"0.00"
                            },
                            "discount":{
                                "currency_code":"GBP",
                                "value":"0.00"
                            }
                        }
                    },
                    "items":[
                        {
                            "name":"xxx",
                            "sku":"xxx",
                            "currency":"GBP",
                            "quantity":1,
                            "category":"PHYSICAL_GOODS",
                            "unit_amount":{
                                "currency_code":"GBP",
                                "value":"305.00"
                            }
                        }
                    ]
                }
            }
        ]
    }

}

Response:

{
  "ack":"contingency",
  "contingency":"UNPROCESSABLE_ENTITY",
  "data": {
    "name":"UNPROCESSABLE_ENTITY",
    "details":[{ 
      "location":"body",
      "issue":"CANNOT_MIX_CURRENCIES",
      "description":"CANNOT_MIX_CURRENCIES"
    }],
    "message":"The requested action could not be performed, semantically incorrect, or failed business validation.",
    "debug_id":"xxx",
    "links":[{
      "href":"https://developer.paypal.com/docs/api/orders/v2/#error-CANNOT_MIX_CURRENCIES",
      "rel":"information_link",
      "method":"GET"
    }]
  },
  "meta":{"calc":"xxx","rlog":"xxx"},
  "server":"xxx"
}

Below is the request (and response) for the creation of the purchase unit above.

Request: POST -> POST -> www.sandbox.paypal.com/v2/checkout/orders

{
    "intent":"CAPTURE",
    "purchase_units":[
        {
            "reference_id":"xxx",
            "invoice_id":"xxx",
            "custom_id":1,
            "description":"xxx",
            "amount":{
                "currency_code":"USD",
                "value":"345.00",
                "breakdown":{
                    "item_total":{
                        "currency_code":"USD",
                        "value":"345.00"
                    },
                    "shipping":{
                        "currency_code":"USD",
                        "value":"0.00"
                    },
                    "tax_total":{
                        "currency_code":"USD",
                        "value":"0.00"
                    },
                    "discount":{
                        "currency_code":"USD",
                        "value":"0.00"
                    }
                }
            },
            "items":[
                {
                    "name":"xxx",
                    "sku":"xxx",
                    "currency":"USD",
                    "quantity":1,
                    "category":"PHYSICAL_GOODS",
                    "unit_amount":{
                        "currency_code":"USD",
                        "value":"345.00"
                    }
                }
            ]
        }
    ],
    "application_context":{
        "shipping_preference":"GET_FROM_FILE"
    }
}

Response:

{
    "id":"xxx",
    "status":"CREATED",
    "links":[
        {
            "href":"https://api.sandbox.paypal.com/v2/checkout/orders/xxx",
            "rel":"self",
            "method":"GET"
        },
        {
            "href":"https://www.sandbox.paypal.com/checkoutnow?token=xxx",
            "rel":"approve",
            "method":"GET"
        },
        {
            "href":"https://api.sandbox.paypal.com/v2/checkout/orders/xxx",
            "rel":"update",
            "method":"PATCH"
        },
        {
            "href":"https://api.sandbox.paypal.com/v2/checkout/orders/xxx/capture",
            "rel":"capture",
            "method":"POST"
        }
    ]
}

I am using the PayPal Orders API v2, via the react-paypal-button-v2 npm package.

The documentation for "CANNOT_MIX_CURRENCIES" mentions all currencies in the purchase unit must be the same, but it does not mention that a purchase unit currency change is not possible. Something confusing is that I could only find mention of this error ("CANNOT_MIX_CURRENCIES") in Billing Agreements API v1.

CANNOT_MIX_CURRENCIES

The currency code is not valid. All currency codes much match. Use same currency code for all amount objects.


Solution

  • In the <script> line where you're loading the SDK, currency defaults to USD, and this SDK line currency matters because it is used to determine which buttons can be shown before the orders v2 object is ever called.

    If you need to change the currency after page load time, you can reload the SDK asynchronously.

    function loadAsync(url, callback) {
        var s = document.createElement('script');
        s.setAttribute('src', url); s.onload = callback;
        document.head.insertBefore(s, document.head.firstElementChild);
    }
    
    // Usage:
    
    loadAsync('https://www.paypal.com/sdk/js?client-id=sb&currency=USD', function() {
      paypal.Buttons({
        createOrder: function(data, actions) {
            //your code here
        },
        onApprove: function(data, actions) {
            //your code here
        }
      }).render('body');  // Replace with selector to render in
    });
    

    Alternatively there is a node package, if you like dependencies more than 3 line functions :) https://github.com/paypal/paypal-js