For anyone still wondering, this might be one of the reasons why you’re seeing the widget twice. The issue I had with Afterpay messages rendering twice was because I was initialising Afterpay in theme.js as well when trying to update the Afterpay price to match the selected variant price.
Because Afterpay.init($) in theme.js was executing before the Afterpay widget rendered the html element to the DOM, the element was rendered twice. To get around this I’ve added a condition in theme.js to check if the element existed on the page yet or not. If it is then remove the existing element from the DOM, update the price then initialise the Afterpay messaging widget again.
//Check the afterpay message element exists on the page
if($('afterpay-placement').length){
//Remove the element from the DOM
$('afterpay-placement').remove();
//Update the afterpay_current_variant object with updated price
afterpay_current_variant.price = [UPDATED_PRICE];
//Render new message element to the DOM
Afterpay.initProductPage($);
} else {
//If the element hasn't rendered yet, simply change the
//afterpay_current_variant object price and the message
//element will render to the DOM with the desired price.
afterpay_current_variant.price = [UPDATED_PRICE];
}