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

Currency #8

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/bidmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ var CONSTANTS = require('./constants.json');
var AUCTION_END = CONSTANTS.EVENTS.AUCTION_END;
var utils = require('./utils.js');
var events = require('./events');
var currency = require('./currency.js');

var objectType_function = 'function';

var externalCallbacks = {byAdUnit: [], all: [], oneTime: null, timer: false};
var _granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM;
var _currencyMultiplier = 1;
let _customPriceBucket;
var defaultBidderSettingsMap = {};

Expand Down Expand Up @@ -90,9 +92,10 @@ function getBidderRequest(bidder, adUnitCode) {
}

/*
* This function should be called to by the bidder adapter to register a bid response
* This function should be called to by the bidder adapter to register a bid response
* (Currency support added via decorator)
*/
exports.addBidResponse = function (adUnitCode, bid) {
exports.addBidResponse = currency.addBidResponseDecorator(function(adUnitCode, bid) {
if (!adUnitCode) {
utils.logWarn('No adUnitCode supplied to addBidResponse, response discarded');
return;
Expand Down Expand Up @@ -125,7 +128,7 @@ exports.addBidResponse = function (adUnitCode, bid) {
events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bid);

//append price strings
const priceStringsObj = getPriceBucketString(bid.cpm, _customPriceBucket);
const priceStringsObj = getPriceBucketString(bid.cpm, _customPriceBucket, _currencyMultiplier);
bid.pbLg = priceStringsObj.low;
bid.pbMg = priceStringsObj.med;
bid.pbHg = priceStringsObj.high;
Expand All @@ -150,7 +153,7 @@ exports.addBidResponse = function (adUnitCode, bid) {
if (bidsBackAll()) {
exports.executeCallback();
}
};
});

function getKeyValueTargetingPairs(bidderCode, custBidObj) {
var keyValues = {};
Expand Down Expand Up @@ -223,7 +226,7 @@ function setKeys(keyValues, bidderSettings, custBidObj) {
return keyValues;
}

exports.setPriceGranularity = function setPriceGranularity(granularity) {
exports.setPriceGranularity = function setPriceGranularity(granularity, currencyMultiplier) {
var granularityOptions = CONSTANTS.GRANULARITY_OPTIONS;
if (Object.keys(granularityOptions).filter(option => granularity === granularityOptions[option])) {
_granularity = granularity;
Expand All @@ -232,6 +235,9 @@ exports.setPriceGranularity = function setPriceGranularity(granularity) {
' `medium` as default.');
_granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM;
}
if (currencyMultiplier !== undefined) {
_currencyMultiplier = currencyMultiplier;
}
};

exports.registerDefaultBidderSetting = function (bidderCode, defaultSetting) {
Expand Down
8 changes: 5 additions & 3 deletions src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"hb_size",
"hb_deal"
],
"S2S" : {
"DEFAULT_ENDPOINT" : "https://prebid.adnxs.com/pbs/v1/auction"
}
"S2S": {
"DEFAULT_ENDPOINT": "https://prebid.adnxs.com/pbs/v1/auction"
},
"CURRENCY_RATE_PRECISION": 4,
"DEFAULT_CURRENCY_RATE_URL": "http://don7oq205h0l7.cloudfront.net/latest.json"
Copy link
Author

Choose a reason for hiding this comment

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

This is meant to be replaced once the prebid.org AWS account is activated.

}
35 changes: 18 additions & 17 deletions src/cpmBucketManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,27 @@ const _autoPriceConfig = {
}]
};

function getPriceBucketString(cpm, customConfig) {
function getPriceBucketString(cpm, customConfig, currencyMultiplier) {
currencyMultiplier = currencyMultiplier || 1;
let cpmFloat = 0;
cpmFloat = parseFloat(cpm);
if (isNaN(cpmFloat)) {
cpmFloat = '';
}

return {
low: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _lgPriceConfig),
med: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _mgPriceConfig),
high: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _hgPriceConfig),
auto: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _autoPriceConfig),
dense: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _densePriceConfig),
custom: (cpmFloat === '') ? '' : getCpmStringValue(cpm, customConfig)
low: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _lgPriceConfig, currencyMultiplier),
med: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _mgPriceConfig, currencyMultiplier),
high: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _hgPriceConfig, currencyMultiplier),
auto: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _autoPriceConfig, currencyMultiplier),
dense: (cpmFloat === '') ? '' : getCpmStringValue(cpm, _densePriceConfig, currencyMultiplier),
custom: (cpmFloat === '') ? '' : getCpmStringValue(cpm, customConfig, currencyMultiplier)
};
}

function getCpmStringValue(cpm, config) {
function getCpmStringValue(cpm, config, currencyMultiplier) {
let cpmStr = '';
if (!isValidePriceConfig(config)) {
if (!isValidPriceConfig(config)) {
return cpmStr;
}
const cap = config.buckets.reduce((prev,curr) => {
Expand All @@ -86,20 +87,20 @@ function getCpmStringValue(cpm, config) {
'max': 0,
});
let bucket = config.buckets.find(bucket => {
if (cpm > cap.max) {
if (cpm > cap.max * currencyMultiplier) {
const precision = bucket.precision || _defaultPrecision;
cpmStr = bucket.max.toFixed(precision);
} else if (cpm <= bucket.max && cpm >= bucket.min) {
cpmStr = (bucket.max * currencyMultiplier).toFixed(precision);
} else if (cpm <= bucket.max * currencyMultiplier && cpm >= bucket.min * currencyMultiplier) {
return bucket;
}
});
if (bucket) {
cpmStr = getCpmTarget(cpm, bucket.increment, bucket.precision);
cpmStr = getCpmTarget(cpm, bucket.increment, bucket.precision, currencyMultiplier);
}
return cpmStr;
}

function isValidePriceConfig(config) {
function isValidPriceConfig(config) {
if (!config || !config.buckets || !Array.isArray(config.buckets)) {
return false;
}
Expand All @@ -112,12 +113,12 @@ function isValidePriceConfig(config) {
return isValid;
}

function getCpmTarget(cpm, increment, precision) {
function getCpmTarget(cpm, increment, precision, currencyMultiplier) {
if (!precision) {
precision = _defaultPrecision;
}
let bucketSize = 1 / increment;
let bucketSize = 1 / increment * currencyMultiplier;
return (Math.floor(cpm * bucketSize) / bucketSize).toFixed(precision);
}

export { getPriceBucketString, isValidePriceConfig };
export { getPriceBucketString, isValidPriceConfig };
151 changes: 151 additions & 0 deletions src/currency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import bidfactory from 'src/bidfactory';

var CONSTANTS = require('./constants');
var utils = require('./utils.js');
var ajax = require('./ajax.js').ajax;

var currency = exports;

var bidResponseQueue = [];
var conversionCache = {};

currency.currencySupportEnabled = false;
currency.currencyRatesLoaded = false;
currency.currencyRates = {};

currency.initCurrencyRates = function(url) {
utils.logInfo('Invoking currency.initCurrencyRates', arguments);

conversionCache = {};
currency.currencySupportEnabled = true;

ajax(url, function(response) {
currency.currencyRates = JSON.parse(response);
Copy link

Choose a reason for hiding this comment

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

may want to handle an exception parsing the response.

currency.currencyRatesLoaded = true;
utils.logInfo('currencyRates set to ' + JSON.stringify(currency.currencyRates));

currency.processBidResponseQueue();
});
}

currency.resetCurrencyRates = function() {
conversionCache = {};
currency.currencySupportEnabled = false;
currency.currencyRatesLoaded = false;
currency.currencyRates = {};
}

currency.addBidResponseDecorator = function(fn) {
return function() {
utils.logInfo('Invoking addBidResponseDecorator function', arguments);

bidResponseQueue.push(wrapFunction(fn, this, arguments));
if (!currency.currencySupportEnabled || (currency.currencySupportEnabled && currency.currencyRatesLoaded)) {
Copy link

Choose a reason for hiding this comment

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

no need to check currency.currencySupportEnabled again after the ||. This is logically equivalent:
if (!currency.currencySupportEnabled || currency.currencyRatesLoaded) {

currency.processBidResponseQueue();
}
}
}

currency.processBidResponseQueue = function() {
utils.logInfo('Invoking processBidResponseQueue', arguments);

while (bidResponseQueue.length >0) {
(bidResponseQueue.shift())();
}
}

function wrapFunction(fn, context, params) {
return function() {
utils.logInfo('Invoking wrapped function', arguments);
var bid = params[1];
if (bid !== undefined && 'currency' in bid && 'cpm' in bid) {
var fromCurrency = bid.currency;
try {
var conversion = getCurrencyConversion(fromCurrency);
bid.originalCpm = bid.cpm;
bid.originalCurrency = bid.currency;
if (conversion !== 1) {
bid.cpm = (parseFloat(bid.cpm) * conversion).toFixed(4);
bid.currency = $$PREBID_GLOBAL$$.pageConfig.currency.adServerCurrency;
}
} catch (e) {
utils.logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e);
params[1] = bidfactory.createBid(CONSTANTS.STATUS.NO_BID);
}
}
return fn.apply(context, params);
};
}

function getCurrencyConversion(fromCurrency) {
utils.logInfo('Invoking getCurrencyConversion', arguments);

var conversionRate = null;

if (fromCurrency in conversionCache) {
conversionRate = conversionCache[fromCurrency];
utils.logMessage('Using conversionCache value ' + conversionRate + ' for fromCurrency ' + fromCurrency);

} else if (currency.currencySupportEnabled === false) {
if (fromCurrency === 'USD') {
conversionRate = 1;
} else {
throw new Error('Prebid currency support has not been enabled with initCurrencyRates and fromCurrency is not USD');
}

} else if (fromCurrency === $$PREBID_GLOBAL$$.pageConfig.currency.adServerCurrency) {
conversionRate = 1;

} else {

var toCurrency = $$PREBID_GLOBAL$$.pageConfig.currency.adServerCurrency;

if (fromCurrency in currency.currencyRates.conversions) {

// using direct conversion rate from fromCurrency to toCurrency
var rates = currency.currencyRates.conversions[fromCurrency];
if (!(toCurrency in rates)) {
// bid should fail, currency is not supported
throw new Error('Specified adServerCurrency in config \'' + toCurrency + '\' not found in the currency rates file');
}
conversionRate = rates[toCurrency];
utils.logInfo('currency.getCurrencyConversion using direct ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate);

} else if (toCurrency in currency.currencyRates.conversions) {

// using reciprocal of conversion rate from toCurrency to fromCurrency
var rates = currency.currencyRates.conversions[toCurrency];
if (!(fromCurrency in rates)) {
// bid should fail, currency is not supported
throw new Error('Specified fromCurrency \'' + fromCurrency + '\' not found in the currency rates file');
}
conversionRate = utils.roundFloat(1 / rates[fromCurrency], CONSTANTS.CURRENCY_RATE_PRECISION);
utils.logInfo('currency.getCurrencyConversion using reciprocal ' + fromCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate);

} else {

// first defined currency base used as intermediary
var anyBaseCurrency = Object.keys(currency.currencyRates.conversions)[0];

if (!(fromCurrency in currency.currencyRates.conversions[anyBaseCurrency])) {
// bid should fail, currency is not supported
throw new Error('Specified fromCurrency \'' + fromCurrency + '\' not found in the currency rates file');
}
var toIntermediateConversionRate = 1 / currency.currencyRates.conversions[anyBaseCurrency][fromCurrency];

if (!(toCurrency in currency.currencyRates.conversions[anyBaseCurrency])) {
// bid should fail, currency is not supported
throw new Error('Specified adServerCurrency in config \'' + toCurrency + '\' not found in the currency rates file');
}
var fromIntermediateConversionRate = currency.currencyRates.conversions[anyBaseCurrency][toCurrency];

conversionRate = utils.roundFloat(toIntermediateConversionRate * fromIntermediateConversionRate, CONSTANTS.CURRENCY_RATE_PRECISION);
utils.logInfo('currency.getCurrencyConversion using intermediate ' + fromCurrency + ' thru ' + anyBaseCurrency + ' to ' + toCurrency + ' conversionRate ' + conversionRate);
}
}
if (!(fromCurrency in conversionCache)) {
utils.logMessage('Adding conversionCache value ' + conversionRate + ' for fromCurrency ' + fromCurrency);
conversionCache[fromCurrency] = conversionRate;
}
return conversionRate;
}
Loading