Skip to content

Commit

Permalink
Prebid Core: Functionality to Optionally Defer Billing for an Ad (pre…
Browse files Browse the repository at this point in the history
…bid#9640)

* logic for billing deferrals

* refactored and addressed feedback

* refactored triggerBilling func

* addressed feedback

* rebased on top of master

* reverted some unneeded changes

* refactored triggerBilling and addWinningBid funcs

* reverted changes to example html file

* addressed changes from feedback
  • Loading branch information
jlquaccia committed May 5, 2023
1 parent bc665aa commit 879e5c4
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,10 @@ adapterManager.callBidWonBidder = function(bidder, bid, adUnits) {
tryCallBidderMethod(bidder, 'onBidWon', bid);
};

adapterManager.callBidBillableBidder = function(bid) {
tryCallBidderMethod(bid.bidder, 'onBidBillable', bid);
};

adapterManager.callSetTargetingBidder = function(bidder, bid) {
tryCallBidderMethod(bidder, 'onSetTargeting', bid);
};
Expand Down
2 changes: 2 additions & 0 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
}

function addWinningBid(winningBid) {
const winningAd = adUnits.find(adUnit => adUnit.transactionId === winningBid.transactionId);
_winningBids = _winningBids.concat(winningBid);
adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits);
if (winningAd && !winningAd.deferBilling) adapterManager.callBidBillableBidder(winningBid);
}

function setBidTargeting(bid) {
Expand Down
48 changes: 36 additions & 12 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -1001,25 +1001,31 @@ if (FEATURES.VIDEO) {
* @alias module:pbjs.markWinningBidAsUsed
*/
pbjsInstance.markWinningBidAsUsed = function (markBidRequest) {
let bids = [];

if (markBidRequest.adUnitCode && markBidRequest.adId) {
bids = auctionManager.getBidsReceived()
.filter(bid => bid.adId === markBidRequest.adId && bid.adUnitCode === markBidRequest.adUnitCode);
} else if (markBidRequest.adUnitCode) {
bids = targeting.getWinningBids(markBidRequest.adUnitCode);
} else if (markBidRequest.adId) {
bids = auctionManager.getBidsReceived().filter(bid => bid.adId === markBidRequest.adId);
} else {
logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.');
}
const bids = fetchReceivedBids(markBidRequest, 'Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.');

if (bids.length > 0) {
bids[0].status = CONSTANTS.BID_STATUS.RENDERED;
}
}
}

const fetchReceivedBids = (bidRequest, warningMessage) => {
let bids = [];

if (bidRequest.adUnitCode && bidRequest.adId) {
bids = auctionManager.getBidsReceived()
.filter(bid => bid.adId === bidRequest.adId && bid.adUnitCode === bidRequest.adUnitCode);
} else if (bidRequest.adUnitCode) {
bids = targeting.getWinningBids(bidRequest.adUnitCode);
} else if (bidRequest.adId) {
bids = auctionManager.getBidsReceived().filter(bid => bid.adId === bidRequest.adId);
} else {
logWarn(warningMessage);
}

return bids;
};

/**
* Get Prebid config options
* @param {Object} options
Expand Down Expand Up @@ -1097,4 +1103,22 @@ pbjsInstance.processQueue = function () {
processQueue(pbjsInstance.cmd);
};

/**
* @alias module:pbjs.triggerBilling
*/
pbjsInstance.triggerBilling = (winningBid) => {
const bids = fetchReceivedBids(winningBid, 'Improper use of triggerBilling. It requires a bid with at least an adUnitCode or an adId to function.');
const triggerBillingBid = bids.find(bid => bid.requestId === winningBid.requestId) || bids[0];

if (bids.length > 0 && triggerBillingBid) {
try {
adapterManager.callBidBillableBidder(triggerBillingBid);
} catch (e) {
logError('Error when triggering billing :', e);
}
} else {
logWarn('The bid provided to triggerBilling did not match any bids received.');
}
};

export default pbjsInstance;
60 changes: 59 additions & 1 deletion test/spec/unit/pbjs_api_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2501,7 +2501,6 @@ describe('Unit: Prebid Module', function () {
}];
let adUnitCodes = ['adUnit-code'];
let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout});

adUnits[0]['mediaTypes'] = { native: {} };
adUnitCodes = ['adUnit-code'];
let auction1 = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout});
Expand Down Expand Up @@ -3546,4 +3545,63 @@ describe('Unit: Prebid Module', function () {
expect(bids[0].adId).to.equal('adid-1');
});
});

describe('deferred billing', function () {
const sandbox = sinon.createSandbox();

let adUnits = [
{
code: 'adUnit-code-1',
mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } },
transactionId: '1234567890',
bids: [
{ bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1' }
]
},
{
code: 'adUnit-code-2',
deferBilling: true,
mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } },
transactionId: '0987654321',
bids: [
{ bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2' }
]
}
];

let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', transactionId: '1234567890', adId: 'abcdefg' }
let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', transactionId: '0987654321' }
let adUnitCodes = ['adUnit-code-1', 'adUnit-code-2'];
let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000});

beforeEach(function () {
sandbox.spy(adapterManager, 'callBidWonBidder');
sandbox.spy(adapterManager, 'callBidBillableBidder');
sandbox.stub(auctionManager, 'getBidsReceived').returns([winningBid1]);
});

afterEach(function () {
sandbox.resetHistory();
sandbox.restore();
});

it('should by default invoke callBidWonBidder and callBidBillableBidder', function () {
auction.addWinningBid(winningBid1);
sinon.assert.calledOnce(adapterManager.callBidWonBidder);
sinon.assert.calledOnce(adapterManager.callBidBillableBidder);
});

it('should only invoke callBidWonBidder and NOT callBidBillableBidder if deferBilling is present and true within the winning adUnit object', function () {
auction.addWinningBid(winningBid2);
sinon.assert.calledOnce(adapterManager.callBidWonBidder);
sinon.assert.notCalled(adapterManager.callBidBillableBidder);
});

it('should invoke callBidBillableBidder when pbjs.triggerBilling is invoked', function () {
$$PREBID_GLOBAL$$.triggerBilling(winningBid1);
sinon.assert.calledOnce(auctionManager.getBidsReceived);
sinon.assert.notCalled(adapterManager.callBidWonBidder);
sinon.assert.calledOnce(adapterManager.callBidBillableBidder);
});
});
});

0 comments on commit 879e5c4

Please sign in to comment.