javascriptc#asp.netpaypal

Disable PayPal "Ship to billing address" option


Our ASP.NET app sells only digital goods and doesn't need the "Ship to billing address" checkbox shown. Using the code example from the PayPal Checkout documentation, I've got the following index.html page:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>PayPal JS SDK Standard Integration</title>
</head>
<body>
    <div id="paypal-button-container"></div>
    <p id="result-message"></p>

    <!-- Initialize the JS-SDK -->
    <script src="https://www.paypal.com/sdk/js?client-id=<my_client_id>&buyer-country=US&currency=USD&components=buttons&enable-funding=card&commit=false&disable-funding=paylater&debug=true"
            data-sdk-integration-source="developer-studio"></script>
    <script src="Scripts/paypal/app.js"></script>
</body>
</html>

The code in app.js is:

const paypalButtons = window.paypal.Buttons({
    style: {
        shape: "rect",
        layout: "vertical",
        color: "gold",
        label: "paypal",
    },
    message: {
        amount: 100,
    },
    
    async createOrder() {
        try {
            const response = await fetch("/api/paypal/orders", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                // use the "body" param to optionally pass additional order information
                // like product ids and quantities
                body: JSON.stringify({
                    cart: [
                        {
                            id: "YOUR_PRODUCT_ID",
                            quantity: "YOUR_PRODUCT_QUANTITY",
                        },
                    ]
                })
            });

            const orderData = await response.json();

            if (orderData.id) {
                return orderData.id;
            }
            const errorDetail = orderData?.details?.[0];
            const errorMessage = errorDetail
                ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
                : JSON.stringify(orderData);

            throw new Error(errorMessage);
        } catch (error) {
            console.error(error);
            // resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
        }
    },
    async onApprove(data, actions) {
        try {
            const response = await fetch(
                `/api/paypal/orders/${data.orderID}/capture`,
                {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                    },
                }
            );

            const orderData = await response.json();
            // Three cases to handle:
            //   (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
            //   (2) Other non-recoverable errors -> Show a failure message
            //   (3) Successful transaction -> Show confirmation or thank you message

            const errorDetail = orderData?.details?.[0];

            if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
                // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
                // recoverable state, per
                // https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
                return actions.restart();
            } else if (errorDetail) {
                // (2) Other non-recoverable errors -> Show a failure message
                throw new Error(
                    `${errorDetail.description} (${orderData.debug_id})`
                );
            } else if (!orderData.purchase_units) {
                throw new Error(JSON.stringify(orderData));
            } else {
                // (3) Successful transaction -> Show confirmation or thank you message
                // Or go to another URL:  actions.redirect('thank_you.html');
                const transaction =
                    orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
                    orderData?.purchase_units?.[0]?.payments
                        ?.authorizations?.[0];
                resultMessage(
                    `Transaction ${transaction.status}: ${transaction.id}<br>
          <br>See console for all available details`
                );
                console.log(
                    "Capture result",
                    orderData,
                    JSON.stringify(orderData, null, 2)
                );
            }
        } catch (error) {
            console.error(error);
            resultMessage(
                `Sorry, your transaction could not be processed...<br><br>${error}`
            );
        }
    }
});
paypalButtons.render("#paypal-button-container");

// Example function to show a result to the user. Your site's UI library can be used instead.
function resultMessage(message) {
    const container = document.querySelector("#result-message");
    container.innerHTML = message;
}

The above code needs to include the following to disable the shipping option but where do you add it?

application_context: {
    shipping_preference: "NO_SHIPPING"
}

This question has application_context but note that it doesn't have anywhere to put the server API endpoint for the fetch as in my code above.

Your help is appreciated.

Update: I tried adding the application_context to the body param and it had no effect:

body: JSON.stringify({
    cart: [
        {
            id: "YOUR_PRODUCT_ID",
            quantity: "YOUR_PRODUCT_QUANTITY",
        },
    ],
    application_context: {
        shipping_preference: "NO_SHIPPING"
    }
})

enter image description here

Update 2: Here's the relevant C# code (.NET 4.8). Note the PaymentSource but what do you add to it?

[System.Web.Http.Route("orders")]
[System.Web.Http.HttpPost]
public async Task<IHttpActionResult> CreateOrderAsync([FromBody] dynamic cart)
{
    try
    {
        var result = await _CreateOrderAsync(cart);
        return Json(result.Data);
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine("Failed to create order:", ex);
        return GetInternalServerError("Failed to create order.");
    }
}

private async Task<dynamic> _CreateOrderAsync(dynamic cart)
{
    var createOrderInput = new CreateOrderInput
    {
        Body = new OrderRequest
        {
            Intent = _paymentIntentMap["CAPTURE"],
            PaymentSource = new PaymentSource
            {
                // What goes here?
            },
            PurchaseUnits = new List<PurchaseUnitRequest>
            {
                new PurchaseUnitRequest
                {
                    Amount = new AmountWithBreakdown
                    {
                        CurrencyCode = "USD",
                        MValue = "100",
                        Breakdown = new AmountBreakdown
                        {
                            ItemTotal = new Money
                            {
                                CurrencyCode = "USD",
                                MValue = "100",
                            }
                        }
                    },
                    // lookup item details in `cart` from database
                    Items = new List<Item>
                    {
                        new Item
                        {
                            Name = "T-Shirt",
                            UnitAmount = new Money
                            {
                                CurrencyCode = "USD",
                                MValue = "100",
                            },
                            Quantity = "1",
                            Description = "Super Fresh Shirt",
                            Sku = "sku01",
                            Category = ItemCategory.DigitalGoods,
                        },
                    }
                }
            }
        }
    };

    ApiResponse<Order> result = await _ordersController.CreateOrderAsync(createOrderInput);
    return result;
}

protected IHttpActionResult GetInternalServerError(string content)
{
    var message = new HttpResponseMessage(HttpStatusCode.InternalServerError)
    {
        Content = new StringContent(content)
    };
    return new ResponseMessageResult(message);
}

Update 3: Corrected the links in the post. Apologies for the mistake earlier.


Solution: Here's code in the _CreateOrderAsync() controller action that fixed it, thanks to @PrestonPHX:

PaymentSource = new PaymentSource
{
    Paypal = new PaypalWallet
    {
        ExperienceContext = new PaypalWalletExperienceContext
        {
            ShippingPreference = PaypalWalletContextShippingPreference.NoShipping
        }
    }
},

Solution

    1. application_context is deprecated. Use payment_source.paypal

    2. The place to add it is not in your front-end code, nor in the "body" your front-end code sends to your backend at "/api/paypal/orders" (or whatever local path you use). Rather, the place to add it is within that server-side route, when it communicates with the PayPal Orders API to create the order. You have not included this code in your question so we cannot advise further.

    Edit: per comments, https://developer.paypal.com/serversdk/net-standard-library/api-endpoints/orders/create-order is being used, and all the documentation on what can be set is there. Something like this should work (not tested)

            var orderRequest = new OrderRequest
            {
                Intent = OrderRequest.IntentType.CAPTURE,
                PurchaseUnits = new List<PurchaseUnit>
                {
                    new PurchaseUnit
                    {
                        Amount = new Amount
                        {
                            CurrencyCode = "USD",
                            Value = "100.00"
                        }
                    }
                },
                PaymentSource = new PaymentSource
                {
                    Paypal = new PaypalWallet
                    {
                        ExperienceContext = new PaypalWalletExperienceContext
                        {
                            ShippingPreference = ExperienceContext.ShippingPreferenceType.NO_SHIPPING
                        }
                    }
                }
            };