Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prebid Core: Functionality to Optionally Defer Billing for an Ad #9640

Merged
merged 9 commits into from
May 5, 2023

Conversation

jlquaccia
Copy link
Collaborator

Type of change

  • Feature

Description of change

  • Added the functionality for a Publisher to optionally defer billing on a specific ad unit if desired

Logic follows the proposal outlined here:

  1. bid adapters can provide a new method onBidBillable(bid) which will be invoked by core when it deems a bid to be billable;
  2. when a bid wins, it is by default also billable; that is, by default, core will invoke onBidWon and onBidBillable one after the other;
  3. publisher can flag adUnits as being separately billable: pbjs.addAdUnits({deferBilling: true, ...})
  4. winning bids for those adUnits trigger onBidWon but not onBidBillable;
  5. when appropriate (e.g. interstitial is displayed), the publisher calls a new public API, pbjs.triggerBilling({adId}), which would trigger onBidBillable.

Note: For this feature to fully be enabled, the following is also required:

  • The bid adapter a Publisher utilizes must support the onBidBillable method
  • A Publisher would have to manually invoke the pbjs.triggerBilling when desired

Other information

Related Issue: #4494

@jlquaccia jlquaccia requested a review from dgirardi March 8, 2023 23:16
@@ -616,6 +616,12 @@ adapterManager.callBidWonBidder = function(bidder, bid, adUnits) {
tryCallBidderMethod(bidder, 'onBidWon', bid);
};

adapterManager.callBidBillableBidder = function(bidder, bid, adUnits) {
// Adding user configured params to bidWon event data
bid.params = getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary. It's already happened in bidWon, and even there I'd question it.

src/auction.js Outdated
function addWinningBid(winningBid) {
function addWinningBid(winningBid, aUnits = adUnits, am = adapterManager) {
const { adUnitCode } = winningBid;
const winningAd = aUnits.find(adUnit => adUnit.code === adUnitCode);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think transactionId is a better ID than code. Multiple adUnits can have the same code.

src/prebid.js Outdated
/**
* @alias module:pbjs.triggerBilling
*/
$$PREBID_GLOBAL$$.triggerBilling = (bidder, bid, adUnits, am = adapterManager) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a public method, so its interface should be kept clean - I don't think you need anything more than bid.

src/auction.js Outdated
@@ -363,9 +363,12 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
}
}

function addWinningBid(winningBid) {
function addWinningBid(winningBid, aUnits = adUnits, am = adapterManager) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is overdoing dependency injection - adUnits is already something you can pass in directly to the constructor; adapterManger could also be if you want it injected, it doesn't make a lot of sense to do it in just one method - the next guy will need to reason about an auction with tightly coupled methods but completely disjointed depedendencies.

@jlquaccia
Copy link
Collaborator Author

thanks @dgirardi, went back to address your feedback and also refactored a bit. let me know if anything else is needed.

src/prebid.js Outdated
/**
* @alias module:pbjs.triggerBilling
*/
$$PREBID_GLOBAL$$.triggerBilling = (bid) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about extracting this piece of code and reusing it here?

what I am worried about is that this call comes from the publisher and here bid could be anything at all; but the adapters will expect it to be an actual certified bid response object.

I would also improve that code a bit to look up by bidId as well. I don't like that it apparently accepts finding multiple matching bids, and just decides that the first one is the "correct" one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @dgirardi, i agree, makes sense to extract that piece of code and reuse it in the triggerBilling func, especially to your point about how a pub could pass through anything.

for the second part of your comment, if/when the bids var gets defined, i added an additional check to make sure that the bid we grab from that array has a matching transactionId with the bid that was passed through the triggerBilling func. should that be sufficient?

@jlquaccia
Copy link
Collaborator Author

@dgirardi do you think anything else is needed for this PR?

src/auction.js Outdated
@@ -367,8 +367,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
}

function addWinningBid(winningBid) {
const { transactionId } = winningBid;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this helps; by the time it gets here, it should already be the correct bid. The filtering happens in markWinningBidAsUsed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for this part I was just aiming to link the winning bid with it's associated adunit to check if deferBilling is true or false. Looks like that can be done by either comparing adUnitCode & code between the two or transactionId. Or let me know if there is another way to do this that I may have missed.

src/prebid.js Outdated
Comment on lines 1102 to 1107
bids = auctionManager.getBidsReceived()
.filter(bid => bid.adId === winningBid.adId && bid.adUnitCode === winningBid.adUnitCode);
} else if (winningBid.adUnitCode) {
bids = targeting.getWinningBids(winningBid.adUnitCode);
} else if (winningBid.adId) {
bids = auctionManager.getBidsReceived().filter(bid => bid.adId === winningBid.adId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be extracted no? as in, re-use code instead of copy-paste.

src/prebid.js Outdated
}

if (bids.length > 0) {
const triggerBillingBid = bids.find(bid => bid.transactionId === winningBid.transactionId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bidId is a better identifier than transactionId; but I also think you need to do something - a warning or so - when you're left with nothing. note that you can only do this in here, not in markWinningBidAsUsed, because that would be a breaking change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed this as well. Had one basic question though, I didn't see bidId on the winning bids but did see requestId which looks to have the same value. Is it safe to you that?

@@ -609,6 +609,10 @@ adapterManager.callBidWonBidder = function(bidder, bid, adUnits) {
tryCallBidderMethod(bidder, 'onBidWon', bid);
};

adapterManager.callBidBillableBidder = function(bid) {
tryCallBidderMethod(bid.adapterCode || bid.bidder, 'onBidBillable', bid);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd leave out the use of adapterCode, because it makes this inconsistent with every other method of this type. (also, if we were to use it, it should be the fallback and not the default).

src/auction.js Outdated
@@ -367,8 +367,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a
}

function addWinningBid(winningBid) {
const winningAd = adUnits.find(adUnit => adUnit.code === winningBid.adUnitCode);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you probably want to use transactionId instead of code here; code is not unique and in theory I could set up deferred billing on only some adUnits that share the same code.

src/prebid.js Outdated
*/
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.adId === winningBid.adId && bid.adUnitCode === winningBid.adUnitCode) || bid.requestId === winningBid.requestId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't fetchReceivedBids just do the searching on adId and adUnitCode? I think those would always be true here.

Also, the match on requestId should be optional here IMO. e.g. const triggerBillingBid = bids.find(bid => bid.requestId === winningBid.requestId) || bids[0].

@jlquaccia
Copy link
Collaborator Author

jlquaccia commented Apr 18, 2023

thanks @dgirardi, the feedback you gave makes sense. just addressed the changes you pointed out with a new commit. if this pr looks good now, would it be possible to merge this one this week? Will free us up over here to work on another ticket that depends on the code in this PR.

@jlquaccia jlquaccia requested a review from ChrisHuie May 3, 2023 18:49
@patmmccann patmmccann changed the title Core: Functionality to Optionally Defer Billing for an Ad Prebid Core: Functionality to Optionally Defer Billing for an Ad May 4, 2023
@patmmccann patmmccann merged commit 879e5c4 into prebid:master May 5, 2023
@bretg
Copy link
Collaborator

bretg commented May 5, 2023

Docs PR?

jorgeluisrocha pushed a commit to jwplayer/Prebid.js that referenced this pull request May 23, 2023
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Support for loading the ad but not firing the billing notifications
5 participants