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,90 @@ 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 User user = request .getUser ();
95+ final Regs regs = request .getRegs ();
96+ final Device device = request .getDevice ();
97+ final Source source = request .getSource ();
98+
99+ final String requestCurrency = resolveCurrency (request .getCur ());
100+ final Price floorInfo = resolveBidFloor (imp , request , requestCurrency );
101+
102+ final MissenaUserParams userParams = MissenaUserParams .builder ()
103+ .formats (extImp .getFormats ())
104+ .placement (extImp .getPlacement ())
105+ .testMode (extImp .getTestMode ())
106+ .settings (extImp .getSettings ())
107+ .build ();
77108
78109 final MissenaAdRequest missenaAdRequest = MissenaAdRequest .builder ()
110+ .adUnit (imp .getId ())
111+ .buyerUid (user != null ? user .getBuyeruid () : null )
112+ .coppa (regs != null ? regs .getCoppa () : null )
113+ .currency (requestCurrency )
114+ .userEids (user != null ? user .getEids () : null )
115+ .floor (floorInfo .getValue ())
116+ .floorCurrency (floorInfo .getCurrency ())
117+ .gdpr (isGdpr (regs ))
118+ .gdprConsent (getUserConsent (user ))
119+ .idempotencyKey (request .getId ())
120+ .referer (site != null ? site .getPage () : null )
121+ .refererCanonical (site != null ? site .getDomain () : null )
79122 .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 ())
123+ .schain (source != null ? source .getSchain () : null )
124+ .timeout (request .getTmax ())
125+ .params (userParams )
126+ .version (prebidVersionProvider .getNameVersionRecord ())
87127 .build ();
88128
89129 return HttpRequest .<MissenaAdRequest >builder ()
90130 .method (HttpMethod .POST )
91- .uri (makeUrl (extImp .getApiKey ()))
92- .headers (makeHeaders (request . getDevice () , site ))
93- .impIds (Collections .singleton (impId ))
131+ .uri (resolveEndpointUrl (extImp .getApiKey ()))
132+ .headers (makeHeaders (device , site ))
133+ .impIds (Collections .singleton (imp . getId () ))
94134 .body (mapper .encodeToBytes (missenaAdRequest ))
95135 .payload (missenaAdRequest )
96136 .build ();
97137 }
98138
139+ private Price resolveBidFloor (Imp imp , BidRequest bidRequest , String targetCurrency ) {
140+ final Price initialBidFloorPrice = Price .of (imp .getBidfloorcur (), imp .getBidfloor ());
141+ return BidderUtil .isValidPrice (initialBidFloorPrice )
142+ ? convertBidFloor (initialBidFloorPrice , imp .getId (), bidRequest , targetCurrency )
143+ : initialBidFloorPrice ;
144+ }
145+
146+ private Price convertBidFloor (Price bidFloorPrice , String impId , BidRequest bidRequest , String targetCurrency ) {
147+ final String bidFloorCur = bidFloorPrice .getCurrency ();
148+
149+ try {
150+ final BigDecimal convertedPrice = currencyConversionService
151+ .convertCurrency (bidFloorPrice .getValue (), bidRequest , bidFloorCur , targetCurrency );
152+
153+ return Price .of (targetCurrency , convertedPrice );
154+ } catch (PreBidException e ) {
155+ throw new PreBidException ("Unable to convert provided bid floor currency from %s to %s for imp `%s`"
156+ .formatted (bidFloorCur , targetCurrency , impId ));
157+ }
158+ }
159+
160+ private String resolveCurrency (List <String > requestCurrencies ) {
161+ String currency = USD_CURRENCY ;
162+
163+ for (String requestCurrency : requestCurrencies ) {
164+ if (USD_CURRENCY .equalsIgnoreCase (requestCurrency )) {
165+ return USD_CURRENCY ;
166+ }
167+
168+ if (EUR_CURRENCY .equalsIgnoreCase (requestCurrency )) {
169+ currency = EUR_CURRENCY ;
170+ }
171+ }
172+
173+ return currency ;
174+ }
175+
99176 private MultiMap makeHeaders (Device device , Site site ) {
100177 final MultiMap headers = HttpUtil .headers ();
101178
@@ -105,15 +182,21 @@ private MultiMap makeHeaders(Device device, Site site) {
105182 HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .X_FORWARDED_FOR_HEADER , device .getIpv6 ());
106183 }
107184
108- if (site != null ) {
185+ if (site != null && StringUtils . isNotBlank ( site . getPage ()) ) {
109186 HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .REFERER_HEADER , site .getPage ());
187+ try {
188+ final URL url = new URL (site .getPage ());
189+ final String origin = url .getProtocol () + "://" + url .getHost ();
190+ HttpUtil .addHeaderIfValueIsNotEmpty (headers , HttpUtil .ORIGIN_HEADER , origin );
191+ } catch (MalformedURLException e ) {
192+ // do nothing
193+ }
110194 }
111-
112195 return headers ;
113196 }
114197
115- private String makeUrl (String apiKey ) {
116- return endpointUrl + "?t=%s" . formatted (apiKey );
198+ private String resolveEndpointUrl (String apiKey ) {
199+ return endpointUrl . replace ( PUBLISHER_ID_MACRO , HttpUtil . encodeUrl (apiKey ) );
117200 }
118201
119202 private static boolean isGdpr (Regs regs ) {
@@ -128,7 +211,8 @@ private static String getUserConsent(User user) {
128211 return Optional .ofNullable (user )
129212 .map (User ::getExt )
130213 .map (ExtUser ::getConsent )
131- .orElse (StringUtils .EMPTY );
214+ .filter (StringUtils ::isNotBlank )
215+ .orElse (null );
132216 }
133217
134218 @ Override
0 commit comments