66import com .iab .openrtb .request .Imp ;
77import com .iab .openrtb .request .Regs ;
88import com .iab .openrtb .request .Site ;
9+ import com .iab .openrtb .request .Source ;
910import com .iab .openrtb .request .User ;
1011import com .iab .openrtb .response .Bid ;
1112import io .vertx .core .MultiMap ;
1617import org .prebid .server .bidder .model .BidderCall ;
1718import org .prebid .server .bidder .model .BidderError ;
1819import org .prebid .server .bidder .model .HttpRequest ;
20+ import org .prebid .server .bidder .model .Price ;
1921import org .prebid .server .bidder .model .Result ;
22+ import org .prebid .server .currency .CurrencyConversionService ;
2023import org .prebid .server .exception .PreBidException ;
2124import org .prebid .server .json .DecodeException ;
2225import org .prebid .server .json .JacksonMapper ;
2528import org .prebid .server .proto .openrtb .ext .request .ExtUser ;
2629import org .prebid .server .proto .openrtb .ext .request .missena .ExtImpMissena ;
2730import org .prebid .server .proto .openrtb .ext .response .BidType ;
31+ import org .prebid .server .util .BidderUtil ;
2832import org .prebid .server .util .HttpUtil ;
33+ import org .prebid .server .version .PrebidVersionProvider ;
2934
35+ import java .math .BigDecimal ;
36+ import java .net .MalformedURLException ;
37+ import java .net .URL ;
3038import java .util .ArrayList ;
3139import java .util .Collections ;
3240import java .util .List ;
@@ -37,31 +45,40 @@ public class MissenaBidder implements Bidder<MissenaAdRequest> {
3745
3846 private static final TypeReference <ExtPrebid <?, ExtImpMissena >> TYPE_REFERENCE = new TypeReference <>() {
3947 };
40- private static final int AD_REQUEST_DEFAULT_TIMEOUT = 2000 ;
48+ private static final String USD_CURRENCY = "USD" ;
49+ private static final String EUR_CURRENCY = "EUR" ;
50+ private static final String PUBLISHER_ID_MACRO = "{{PublisherID}}" ;
4151
4252 private final String endpointUrl ;
4353 private final JacksonMapper mapper ;
54+ private final CurrencyConversionService currencyConversionService ;
55+ private final PrebidVersionProvider prebidVersionProvider ;
56+
57+ public MissenaBidder (String endpointUrl ,
58+ JacksonMapper mapper ,
59+ CurrencyConversionService currencyConversionService ,
60+ PrebidVersionProvider prebidVersionProvider ) {
4461
45- public MissenaBidder (String endpointUrl , JacksonMapper mapper ) {
4662 this .endpointUrl = HttpUtil .validateUrl (Objects .requireNonNull (endpointUrl ));
4763 this .mapper = Objects .requireNonNull (mapper );
64+ this .currencyConversionService = Objects .requireNonNull (currencyConversionService );
65+ this .prebidVersionProvider = Objects .requireNonNull (prebidVersionProvider );
4866 }
4967
5068 @ Override
5169 public Result <List <HttpRequest <MissenaAdRequest >>> makeHttpRequests (BidRequest request ) {
52- final List <HttpRequest <MissenaAdRequest >> requests = new ArrayList <>();
5370 final List <BidderError > errors = new ArrayList <>();
5471
5572 for (Imp imp : request .getImp ()) {
5673 try {
5774 final ExtImpMissena extImp = parseImpExt (imp );
58- requests .add (makeHttpRequest (request , imp .getId (), extImp ));
75+ final HttpRequest <MissenaAdRequest > httpRequest = makeHttpRequest (request , imp , extImp );
76+ return Result .of (Collections .singletonList (httpRequest ), errors );
5977 } catch (PreBidException e ) {
6078 errors .add (BidderError .badInput (e .getMessage ()));
6179 }
6280 }
63-
64- return Result .of (requests , errors );
81+ return Result .withErrors (errors );
6582 }
6683
6784 private ExtImpMissena parseImpExt (Imp imp ) {
@@ -72,30 +89,95 @@ private ExtImpMissena parseImpExt(Imp imp) {
7289 }
7390 }
7491
75- private HttpRequest <MissenaAdRequest > makeHttpRequest (BidRequest request , String impId , ExtImpMissena extImp ) {
92+ private HttpRequest <MissenaAdRequest > makeHttpRequest (BidRequest request , Imp imp , ExtImpMissena extImp ) {
7693 final Site site = request .getSite ();
94+ final String pageUrl = site != null ? site .getPage () : null ;
95+ final User user = request .getUser ();
96+ final Regs regs = request .getRegs ();
97+ final Device device = request .getDevice ();
98+ final Source source = request .getSource ();
99+
100+ final String requestCurrency = resolveCurrency (request .getCur ());
101+ final Price floorInfo = resolveBidFloor (imp , request , requestCurrency );
102+
103+ final MissenaUserParams userParams = MissenaUserParams .builder ()
104+ .formats (extImp .getFormats ())
105+ .placement (extImp .getPlacement ())
106+ .testMode (extImp .getTestMode ())
107+ .settings (extImp .getSettings ())
108+ .build ();
77109
78110 final MissenaAdRequest missenaAdRequest = MissenaAdRequest .builder ()
111+ .adUnit (imp .getId ())
112+ .buyerUid (user != null ? user .getBuyeruid () : null )
113+ .coppa (regs != null ? regs .getCoppa () : null )
114+ .currency (requestCurrency )
115+ .userEids (user != null ? user .getEids () : null )
116+ .floor (floorInfo .getValue ())
117+ .floorCurrency (floorInfo .getCurrency ())
118+ .gdpr (isGdpr (regs ))
119+ .gdprConsent (getUserConsent (user ))
120+ .idempotencyKey (request .getId ())
121+ .referer (pageUrl )
122+ .refererCanonical (site != null ? site .getDomain () : null )
79123 .requestId (request .getId ())
80- .timeout (AD_REQUEST_DEFAULT_TIMEOUT )
81- .referer (site == null ? null : site .getPage ())
82- .refererCanonical (site == null ? null : site .getDomain ())
83- .consentString (getUserConsent (request .getUser ()))
84- .consentRequired (isGdpr (request .getRegs ()))
85- .placement (extImp .getPlacement ())
86- .test (extImp .getTestMode ())
124+ .schain (source != null ? source .getSchain () : null )
125+ .timeout (request .getTmax ())
126+ .params (userParams )
127+ .version (prebidVersionProvider .getNameVersionRecord ())
87128 .build ();
88129
89130 return HttpRequest .<MissenaAdRequest >builder ()
90131 .method (HttpMethod .POST )
91- .uri (makeUrl (extImp .getApiKey ()))
92- .headers (makeHeaders (request . getDevice () , site ))
93- .impIds (Collections .singleton (impId ))
132+ .uri (resolveEndpointUrl (extImp .getApiKey ()))
133+ .headers (makeHeaders (device , site ))
134+ .impIds (Collections .singleton (imp . getId () ))
94135 .body (mapper .encodeToBytes (missenaAdRequest ))
95136 .payload (missenaAdRequest )
96137 .build ();
97138 }
98139
140+ private Price resolveBidFloor (Imp imp , BidRequest bidRequest , String targetCurrency ) {
141+ final Price initialBidFloorPrice = Price .of (imp .getBidfloorcur (), imp .getBidfloor ());
142+ return BidderUtil .isValidPrice (initialBidFloorPrice )
143+ ? convertBidFloor (initialBidFloorPrice , imp .getId (), bidRequest , targetCurrency )
144+ : initialBidFloorPrice ;
145+ }
146+
147+ private Price convertBidFloor (Price bidFloorPrice , String impId , BidRequest bidRequest , String targetCurrency ) {
148+ final String bidFloorCur = bidFloorPrice .getCurrency ();
149+
150+ if (targetCurrency .equalsIgnoreCase (bidFloorCur )) {
151+ return bidFloorPrice ;
152+ }
153+
154+ try {
155+ final BigDecimal convertedPrice = currencyConversionService
156+ .convertCurrency (bidFloorPrice .getValue (), bidRequest , bidFloorCur , targetCurrency );
157+
158+ return Price .of (targetCurrency , convertedPrice );
159+ } catch (PreBidException e ) {
160+ throw new PreBidException ("Unable to convert provided bid floor currency from %s to %s for imp `%s`"
161+ .formatted (bidFloorCur , targetCurrency , impId ));
162+ }
163+ }
164+
165+ private String resolveCurrency (List <String > requestCurrencies ) {
166+ for (String currency : requestCurrencies ) {
167+ if (USD_CURRENCY .equalsIgnoreCase (currency )) {
168+ return USD_CURRENCY ;
169+ }
170+ }
171+
172+ for (String currency : requestCurrencies ) {
173+ if (EUR_CURRENCY .equalsIgnoreCase (currency )) {
174+ return EUR_CURRENCY ;
175+ }
176+ }
177+
178+ return USD_CURRENCY ;
179+ }
180+
99181 private MultiMap makeHeaders (Device device , Site site ) {
100182 final MultiMap headers = HttpUtil .headers ();
101183
@@ -105,15 +187,21 @@ private MultiMap makeHeaders(Device device, Site site) {
105187 HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .X_FORWARDED_FOR_HEADER , device .getIpv6 ());
106188 }
107189
108- if (site != null ) {
190+ if (site != null && StringUtils . isNotBlank ( site . getPage ()) ) {
109191 HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .REFERER_HEADER , site .getPage ());
192+ try {
193+ final URL url = new URL (site .getPage ());
194+ final String origin = url .getProtocol () + "://" + url .getHost ();
195+ HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .ORIGIN_HEADER , origin );
196+ } catch (MalformedURLException e ) {
197+ // do nothing
198+ }
110199 }
111-
112200 return headers ;
113201 }
114202
115- private String makeUrl (String apiKey ) {
116- return endpointUrl + "?t=%s" . formatted (apiKey );
203+ private String resolveEndpointUrl (String apiKey ) {
204+ return endpointUrl . replace ( PUBLISHER_ID_MACRO , HttpUtil . encodeUrl (apiKey ) );
117205 }
118206
119207 private static boolean isGdpr (Regs regs ) {
@@ -128,7 +216,8 @@ private static String getUserConsent(User user) {
128216 return Optional .ofNullable (user )
129217 .map (User ::getExt )
130218 .map (ExtUser ::getConsent )
131- .orElse (StringUtils .EMPTY );
219+ .filter (StringUtils ::isNotBlank )
220+ .orElse (null );
132221 }
133222
134223 @ Override
0 commit comments