1var api = { 2 apitoken: null, 3 refreshtoken: null, 4 path: "/api/v2", 5 version: "2.0", 6 sessionkey: null, 7 brandsessionkey: null, 8 url: function (path) { return `${api.path}/${path}`; }, 9 /* 10 * Run fn with the signed-in USER's session (sent as `sid`) instead of whatever the page set up. 11 * A brand session (`bsid`) authenticates as the brand - the API takes its user id from the brand row, 12 * i.e. the brand owner - which suits brand-scoped calls such as the Titus pre-auths, but not a call that 13 * acts for the shopper (adding their card). Pages like the cart set brandsessionkey for those brand-scoped 14 * calls, so the user session is swapped in only for the duration of fn and then put back. 15 */ 16 asUser: async function (userSessionKey, fn) { 17 var prev = api.sessionkey; 18 api.sessionkey = userSessionKey; 19 try { 20 return await fn(); 21 } finally { 22 api.sessionkey = prev; 23 } 24 }, 25 sqlannotation: s => { 26 if (s.database?.length ?? false) { 27 var grid = $(".adminlogin-grid tbody tr"); 28 if (grid.length) { 29 s.database.forEach(row => { 30 var idx = grid.length - 1; 31 var newRow = $(`<tr><td>${row.duration / 1000}</td><td>API</td><td></td><td>${row.sql}</td></tr>`); 32 newRow.insertBefore($(`.adminlogin-grid tbody tr:nth(${idx})`)); 33 }); 34 } 35 } 36 }, 37 authorize: async function (sessionkey) { 38 if (api.apitoken) { return api.apitoken; } 39 40 var response = await fetch(api.url("Auth/authorize"), { 41 method: "POST", 42 headers: { 43 "Accept": "application/json", 44 "Content-Type": "application/json", 45 "hd-version": "2.0" 46 }, 47 body: JSON.stringify({ "sessionKey": sessionkey }) 48 }); 49 if (response.ok) { 50 var data = await response.json(); 51 api.apitoken = data.accessToken; 52 api.refreshtoken = data.refreshToken; 53 return api.apitoken; 54 } else { 55 return false; 56 } 57 }, 58 refreshToken: async function () { 59 var response = await fetch(api.url('auth/refresh-token'), { 60 method: "POST", 61 headers: { 62 "Accept": "application/json", 63 "Content-Type": "application/json", 64 "hd-version": "2.0" 65 }, 66 body: JSON.stringify({ 67 accessToken: api.apitoken, 68 refreshToken: api.refreshtoken 69 }) 70 }); 71 if (response.ok) { 72 var j = await response.json(); 73 api.apitoken = j.accessToken; 74 api.refreshtoken = j.refreshToken; 75 return true; 76 } 77 78 return false; 79 }, 80 /* 81 * `version` selects the API version via the hd-version header, and DEFAULTS TO "2.0" so every 82 * existing caller behaves exactly as before â the argument is opt-in, added for the v3 UX routes 83 * (ApiVersions.THREE_UX) that legacy pages now need to reach. 84 * 85 * Note the path segment /api/v2/ is NOT the version. It is the ARR reverse-proxy prefix; the 86 * version is this header and nothing else, which is why a v3 route still lives under /api/v2/. 87 */ 88 get: async function (url, data, skipauth, version) { 89 if (skipauth == undefined) { skipauth = false; } 90 91 if (!skipauth) { 92 if (!api.sessionkey && !api.brandsessionkey) { 93 var auth = await api.authorize(); 94 if (!auth) { 95 console.error("could not authorize api"); 96 return false; 97 } 98 } 99 } 100 var url2 = api.url(url); 101 //fetch does not support "body" when "getting" 102 var esc = encodeURIComponent; 103 if (data) { 104 var query = Object.keys(data) 105 .map(k => `${esc(k)}=${esc(data[k])}`) 106 .join('&'); 107 if (query.length) 108 url2 = `${url2}?${query}`; 109 } 110 111 var headers = { 112 "Accept": "application/json", 113 "Content-Type": "application/json", 114 "hd-version": version || "2.0" 115 }; 116 117 if (!skipauth) { 118 var response; 119 if (api.sessionkey) { 120 headers.sid = api.sessionkey; 121 } else if (api.brandsessionkey) { 122 headers.bsid = api.brandsessionkey; 123 } else { 124 headers.Authorization = `Bearer ${api.apitoken}`; 125 } 126 } 127 var response = await fetch(url2, { headers }); 128 129 if (response.ok) { 130 return response; 131 } else { 132 if (response.status == 401) { 133 if (!skipauth) { 134 var r2 = await api.refreshToken(); 135 if (r2) { //re-run the command now that we are re-auth'd â carrying the version, 136 //or the retry would silently drop to 2.0 and 404 a v3 route 137 return await api.get(url, data, skipauth, version); 138 } 139 } 140 } 141 } 142 143 console.error("could not authorize api"); 144 return false;
145 }, 146 /** `version` defaults to "2.0" â see the note on `get`. */ 147 post: async function (url, data, skipauth, version) { 148 if (skipauth == undefined) { skipauth = false; } 149 150 if (!skipauth) { 151 if (!api.sessionkey && !api.brandsessionkey) { 152 var auth = await api.authorize(); 153 if (!auth) { 154 console.error("could not authorize api"); 155 return false; 156 } 157 } 158 } 159 160 var headers = { 161 "Accept": "application/json", 162 "Content-Type": "application/json", 163 "hd-version": version || "2.0" 164 }; 165 166 if (!skipauth) { 167 var response; 168 if (api.sessionkey) { 169 headers.sid = api.sessionkey; 170 } else if (api.brandsessionkey) { 171 headers.bsid = api.brandsessionkey; 172 } else { 173 headers.Authorization = `Bearer ${api.apitoken}`; 174 } 175 } 176 var response = await fetch(api.url(url), { 177 method: "POST", 178 headers, 179 body: data 180 }); 181 182 if (response.ok) { 183 return response; 184 } else { 185 if (response.status == 401) { 186 var r2 = await api.refreshToken(); 187 if (r2) { 188 // Version carried through, or the retry drops to 2.0 and 404s a v3 route. 189 return await api.post(url, data, skipauth, version); 190 } 191 } 192 } 193 return false; 194 }, 195 delete: async function (url, data, skipauth) { 196 if (skipauth == undefined) { skipauth = false; } 197 198 if (!skipauth) { 199 if (!api.sessionkey && !api.brandsessionkey) { 200 var auth = await api.authorize(); 201 if (!auth) { 202 console.error("could not authorize api"); 203 return false; 204 } 205 } 206 } 207 208 var headers = { 209 "Accept": "application/json", 210 "Content-Type": "application/json", 211 "hd-version": "2.0" 212 }; 213 214 if (!skipauth) { 215 var response; 216 if (api.sessionkey) { 217 headers.sid = api.sessionkey; 218 } else if (api.brandsessionkey) { 219 headers.bsid = api.brandsessionkey; 220 } else { 221 headers.Authorization = `Bearer ${api.apitoken}`; 222 } 223 } 224 var response = await fetch(api.url(url), { 225 method: "DELETE", 226 headers, 227 body: data 228 }); 229 230 if (response.ok) { 231 return response; 232 } else { 233 if (response.status == 401) { 234 var r2 = await api.refreshToken(); 235 if (r2) { 236 return await api.post(url, data); 237 } 238 } 239 } 240 return false; 241 }, 242 put: async function (url, data, skipauth) { 243 if (skipauth == undefined) { skipauth = false; } 244 245 if (!skipauth) { 246 if (!api.sessionkey && !api.brandsessionkey) { 247 var auth = await api.authorize(); 248 if (!auth) { 249 console.error("could not authorize api"); 250 return false; 251 } 252 } 253 } 254 255 var headers = { 256 "Accept": "application/json", 257 "Content-Type": "application/json", 258 "hd-version": "2.0" 259 }; 260 261 if (!skipauth) { 262 var response; 263 if (api.sessionkey) { 264 headers.sid = api.sessionkey; 265 } else if (api.brandsessionkey) { 266 headers.bsid = api.brandsessionkey; 267 } else { 268 headers.Authorization = `Bearer ${api.apitoken}`; 269 } 270 } 271 var response = await fetch(api.url(url), { 272 method: "PUT", 273 headers, 274 body: data 275 }); 276 277 if (response.ok) { 278 return response; 279 } else { 280 if (response.status == 401) { 281 var r2 = await api.refreshToken(); 282 if (r2) { 283 return await api.post(url, data); 284 } 285 } 286 } 287 return false;
288 }, 289 /** `version` is passed straight through to `get` â defaults to "2.0" there. */ 290 getAnnotatedSql: async function (url, data, skipauth, version) { 291 var response = await api.get(url, data, skipauth, version); 292 if (response) { 293 if (response.ok) { 294 var j = await response.json(); 295 api.sqlannotation(j); 296 return j.data; 297 } 298 } 299 300 console.error("getAnnotatedSql could not retrieve results"); 301 throw new Error("getAnnotatedSql could not retrieve results"); 302 }, 303 /** `version` is passed straight through to `post` â defaults to "2.0" there. */ 304 postAnnotatedSql: async function (url, data, skipauth, version) { 305 var response = await api.post(url, data, skipauth, version); 306 if (response) { 307 if (response.ok) { 308 var j = await response.json(); 309 api.sqlannotation(j); 310 return j.data; 311 } 312 } 313 314 console.error("postAnnotatedSql could not retrieve results"); 315 throw new Error("postAnnotatedSql could not retrieve results"); 316 }, 317 deleteAnnotatedSql: async function (url, data, skipauth) { 318 var response = await api.delete(url, data, skipauth); 319 if (response) { 320 if (response.ok) { 321 var j = await response.json(); 322 api.sqlannotation(j); 323 return j.data; 324 } 325 } 326 327 console.error("deleteAnnotatedSql could not retrieve results"); 328 throw new Error("deleteAnnotatedSql could not retrieve results"); 329 }, 330 putAnnotatedSql: async function (url, data, skipauth) { 331 var response = await api.put(url, data, skipauth); 332 if (response) { 333 if (response.ok) { 334 var j = await response.json(); 335 api.sqlannotation(j); 336 return j.data; 337 } 338 } 339 340 //BUG-168: this alone of the four annotated helpers used to fall off the end and 341 //return undefined on a failed request, so callers ran their success path anyway. 342 console.error("putAnnotatedSql could not retrieve results"); 343 throw new Error("putAnnotatedSql could not retrieve results"); 344 }, 345 getBrand: async function (bid) { 346 return await api.getAnnotatedSql(`brands/${bid}`); 347 }, 348 getDashClientWelcome: async function () { 349 return await api.getAnnotatedSql("Dash/clientwelcome"); 350 }, 351 getDashUnpaidOrders: async function () { 352 return await api.getAnnotatedSql("dashboards/unpaid-orders"); 353 }, 354 getDashExpiringDomains: async function () { 355 return await api.getAnnotatedSql("Dash/expiringdomains"); //used in MOBILE APP too 356 }, 357 getDashScheduledOrders: async function (qty, offset) { 358 return await api.postAnnotatedSql("dashboards/scheduled-orders", JSON.stringify({ "quantity": qty, "offset": offset })); 359 }, 360 getDashExpiringSites: async function () { 361 return await api.getAnnotatedSql("dashboards/expiring-sites"); 362 }, 363 getDashClientReferralCode: async function () { 364 return await api.getAnnotatedSql("dashboards/client-referral-codes"); 365 }, 366 getDashListOfBusinesses: async function (qty, offset) { 367 return await api.postAnnotatedSql("dashboards/businesses", JSON.stringify({ "quantity": qty, "offset": offset })); 368 }, 369 getDashPreparingSites: async function (qty, offset) { 370 return await api.postAnnotatedSql("dashboards/preparing-sites", JSON.stringify({ "quantity": qty, "offset": offset })); 371 }, 372 getDashActiveProperties: async function (qty, offset) { 373 return await api.postAnnotatedSql("dashboards/active-properties", JSON.stringify({ "quantity": qty, "offset": offset })); 374 }, 375 getDashSuccessWeek: async function (dtstart, dtend) { 376 return await api.postAnnotatedSql("dashboards/success-week", JSON.stringify({ first: dtstart, end: dtend })); 377 }, 378 // getDashTickler: async function (qty, offset) { 379 //return await api.postAnnotatedSql("dashboards/ticklers", JSON.stringify({"quantity": qty, "offset": offset})); 380 // }, 381 getDashNewAccountGoals: async function (dtstart) { 382 return await api.getAnnotatedSql("dashboards/new-account-goals", { from: dtstart }); 383 }, 384 getDashTopBrands: async function (qty) { 385 return await api.getAnnotatedSql(`dashboards/top-brands/${qty}`); 386 }, 387 getDashMonthlyGoals: async function () { 388 return await api.getAnnotatedSql("dashboards/monthly-goals"); 389 }, 390 getDashTopClients: async function () { 391 return await api.getAnnotatedSql("dashboards/top-clients"); 392 }, 393 getDashNewClients: async function () { 394 return await api.getAnnotatedSql("dashboards/new-clients"); 395 }, 396 getDashNeedsToBeCompleted: async function (markets, qty, offset, sort) { 397 return await api.postAnnotatedSql("dashboards/needs-completion", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort })); 398 }, 399 getDashAssignedSitesToBeCompleted: async function (markets, qty, offset, sort) { 400 return await api.postAnnotatedSql("dashboards/assigned-sites-to-complete", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort })); 401 }, 402 getDashNeedsToBeScheduled: async function (markets, qty, offset, sort) { 403 return await api.postAnnotatedSql("dashboards/needs-scheduling", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort })); 404 }, 405 getDashNeedsToBeActivated: async function (qty, offset, sort) { 406 return await api.postAnnotatedSql("dashboards/needs-activation", JSON.stringify({ "quantity": qty, "offset": offset, "sort": sort })); 407 }, 408 getDashQuickStart: async function () { 409 return await api.getAnnotatedSql("dashboards/quick-start"); 410 }, 411 getDashThisWeek: async function (dtstart, dtend, markets) { 412 return await api.postAnnotatedSql("dashboards/this-week", JSON.stringify({ "firstDate": dtstart, "lastDate": dtend, "markets": markets })); 413 }, 414 getDashQuestion: async function () { 415 return await api.getAnnotatedSql("dashboards/questions"); 416 }, 417 getDashUserMessages: async function () { 418 return await api.getAnnotatedSql("dashboards/user-messages"); 419 }, 420 UserMessageDismiss: async function (nusermessageid) { 421 return await api.deleteAnnotatedSql(`dashboards/user-messages/${nusermessageid}`); 422 }, 423 getDashLearningCenter: async function () { 424 return await api.getAnnotatedSql("dashboards/learning-center"); 425 }, 426 getPricingCommodities: async function () { 427 return await api.getAnnotatedSql("pricing/admin/commodities"); 428 }, 429 getPricingCommodity: async function (cid) { 430 return await api.getAnnotatedSql("pricing/admin/commodities/" + cid); 431 }, 432 getPricingPrices: async function (cid, bid, cuid, onlyGlobal, includeDeleted) { 433 var qs = null; 434 if (cid != undefined && cid != null) { 435 qs = (qs ? qs + "&" : "") + "commodityId=" + cid; 436 } 437 if (cuid != undefined && cuid != null) { 438 qs = (qs ? qs + "&" : "") + "currencyId=" + cuid; 439 } 440 if (bid != undefined && bid != null) { 441 qs = (qs ? qs + "&" : "") + "brandId=" + bid; 442 }
443 if (onlyGlobal != undefined && onlyGlobal != null) { 444 qs = (qs ? qs + "&" : "") + "onlyGlobal=" + onlyGlobal; 445 } 446 if (includeDeleted != undefined) { 447 qs = (qs ? qs + "&" : "") + "includeDeleted=" + includeDeleted; 448 } 449 return await api.getAnnotatedSql("pricing/admin/prices" + ((qs != null) ? "?" + qs : "")); 450 }, 451 getPricingCurrencies: async function () { 452 return await api.getAnnotatedSql("pricing/currencies"); 453 }, 454 addRewards: async function (rewards) { 455 return await api.putAnnotatedSql(`pricing/admin/rewards/`, JSON.stringify({ 456 "brandId": rewards.brandId, 457 "commodityId": rewards.commodityId, 458 "userId": rewards.userId, 459 "quantity": rewards.quantity, 460 "note": rewards.note 461 })); 462 }, 463 getRewards: async function (bid) { 464 return await api.getAnnotatedSql(`activations/rewards/${bid ?? ""}`); 465 }, 466 searchBrands: async function (search, sort, qty, offset) { 467 return await this.postAnnotatedSql(`business/search`, JSON.stringify({ 468 "search": search.search, 469 "sort": sort, 470 "quantity": qty, 471 "offset": offset 472 })); 473 }, 474 searchClients: async function (search, sort, qty, offset) { 475 return await api.postAnnotatedSql(`search/clients`, JSON.stringify({ 476 "username": search.username, 477 "email": search.email, 478 "phone": search.phone, 479 "groupname": search.groupname, 480 "userid": search.userid, 481 "usertype": search.usertype, 482 "statusid": search.statusid, 483 "sort": sort, 484 "quantity": qty, 485 "offset": offset 486 })); 487 }, 488 searchGroups: async function (search, sort, qty, offset) { 489 return await api.postAnnotatedSql(`search/groups`, JSON.stringify({ 490 "groupname": search.groupname, 491 "address": search.address, 492 "city": search.city, 493 "region": search.state, 494 "postalCode": search.postalcode, 495 "phone": search.phone, 496 "groupid": search.groupid, 497 "statusid": search.statusid, 498 "sort": sort, 499 "quantity": qty, 500 "offset": offset 501 })); 502 }, 503 searchSitesFuzzy: async function (search, sort, qty, offset) { 504 return await api.postAnnotatedSql(`search/FuzzySites`, JSON.stringify({ 505 "search": search.searchfuzzy, 506 "sort": sort, 507 "quantity": qty, 508 "offset": offset, 509 "statusname": search.statusname 510 })); 511 }, 512 // API-461 changed /sites/search's `shoot` entries from date-only ("2025-02-11") to a brand-local 513 // wall clock with time ("2025-02-11 08:30:00") - offset-less, never UTC. Naively appending 514 // "T00:00" (the old assumption) turns the new shape into "2025-02-11 08:30:00T00:00", which 515 // `new Date(...)` can't parse, producing NaN/NaN in hd.shortDate. Hand-parse both shapes into 516 // explicit date parts instead of leaning on the platform's string parsing. 517 parseWallClock: function (v) { 518 var m = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/.exec(String(v || '').trim()); 519 if (!m) { return null; } 520 return new Date(+m[1], +m[2] - 1, +m[3], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0)); 521 }, 522 searchSitesNew: function (search, sort, qty, offset) { 523 //strip off the first sort, only one allowed in this endpoint 524 sort = sort.split(',')[0]; 525 const map = new Map(); 526 map.set("cr", "created"); 527 map.set("cr desc", "created desc") 528 map.set("br", "bought"); 529 map.set("br desc", "bought desc") 530 map.set("a", "address"); 531 map.set("a desc", "address desc") 532 map.set("st", "city"); 533 map.set("st desc", "city desc") 534 map.set("lp", "price"); 535 map.set("lp desc", "price desc") 536 map.set("un", "client"); 537 map.set("un desc", "client desc") 538 539 //if (sort == "cr") sort = "created"; 540 //if (sort == "cr desc") sort = "created desc"; 541 542 if (sort) { 543 sort = map.get(sort); 544 } 545 546 var encode = function (sizes) { 547 if (sizes?.images) { 548 return sizes.images.map((s, i) => { 549 if (s.url) { 550 var parsed = s.url.split('/').slice(-2).join("/"); 551 var parts = parsed.split("."); 552 parts.pop(); //remove last item; 553 var r = parts.join("."); 554 555 r += `:${s.width}`; 556 r += `:${s.height}`; 557 if (sizes.faces?.length) { 558 r += ":"; 559 r += sizes.faces.map((s, i) => { 560 return s.join('`'); 561 }).join("|"); 562 } 563 564 return r; 565 } 566 567 return null; 568 }).join(','); 569 } 570 571 return null; 572 }; 573 574 var statmap = new Map(); 575 statmap.set("deleted", -1); 576 statmap.set("inactive", 0); 577 statmap.set("active", 1); 578 statmap.set("complete", 5); 579 statmap.set("reserved", 10); 580 return api.getAnnotatedSql(`sites/search?sort=${sort}&quantity=${qty}&offset=${offset}${(search.searchfuzzy ? `&search=${search.searchfuzzy}` : '')}`) 581 .then(data => { 582 if (data) { 583 return data.map(d => { 584 d.id = d.siteId; 585 d.sid = statmap.get(d.status); 586 d.b = d.brandId; 587 d.bid = d.bannerId; 588 d.dc = d.downloadCreditId; 589 d.lc = d.liveCreditId; 590 d.dp = d.downloadPurchaseId; 591 d.lp2 = d.livePurchaseId; 592 d.ac = d.activated; 593 d.lp = d.listPrice; 594 d.a = d.location?.address; 595 d.a2 = d.location?.address2; 596 d.ci = d.location?.city; 597 d.st = d.location?.region; 598 d.zc = d.location?.postalCode; 599 d.m = d.mls; 600 d.n = d.notes; 601 d.cd = d.created; 602 //d.cr = 603 d.bd = d.bought; 604 //d.br = 605 d.osl = d.shootList; 606 d.o = d.oids; 607 d.sIDList = d.idlist; 608 d.sList = d.list; 609 d.sh = d.shoot?.map(sd => hd.shortDate(api.parseWallClock(sd))).join(", "); 610 d.aun = d.assigned; 611 //d.s = 612 d.age = d.daysActive; 613 d.uid = d.clientId; 614 d.cuid = d.coclientId; 615 d.un = d.clientName; 616 d.cun = d.coclientName; 617 d.gn = d.groupName; 618 //d.uids; 619 //d.ss; 620 d.us = encode(d.clientMedia); 621 //d.ue; 622 d.cus = encode(d.coclientMedia); 623 //d.cue; 624 d.s1 = encode(d.media1); 625 //d.s2; 626 //d.s3; 627 //d.s4; 628 //d.e1; 629 //d.e2; 630 //d.e3; 631 //d.e4; 632 633 return d; 634 }); 635 } 636 637 return []; 638 }); 639 }, 640 searchSites: async function (search, sort, qty, offset) { 641 return await api.postAnnotatedSql(`search/sites`, JSON.stringify({ 642 "address": search.address, 643 "city": search.city, 644 "region": search.state, 645 "postalCode": search.postalcode, 646 "username": search.username, 647 "photographername": search.photographername, 648 "siteid": search.siteid, 649 "orderid": search.orderid, 650 "userid": search.userid, 651 "sort": sort, 652 "quantity": qty, 653 "offset": offset, 654 "statusname": search.statusname 655 })); 656 }, 657 iguideMediaUpload: async function (nIGuideOrderID) {
658 return await api.putAnnotatedSql(`vendors/iguide/orders/${nIGuideOrderID}/uploads`); 659 }, 660 iguidePlaceOrder: async function (nOrderProductID) { 661 return await api.postAnnotatedSql(`vendors/iguide/orders/placements`, JSON.stringify({ orderProductId: nOrderProductID })); 662 }, 663 iguideUsers: async function (nBrandID) { 664 return await api.getAnnotatedSql(`vendors/iguide/users`); 665 }, 666 iguideAgentBanners: async function (nAgentUserID, nBrandID) { 667 var qs = `${nAgentUserID ? `?agentId=${nAgentUserID}` : null}`; 668 if (nBrandID) 669 qs += (qs ? '&' : '?') + `brandId=${nBrandID}`; 670 671 return await api.getAnnotatedSql(`vendors/iguide/banners${qs}`); 672 }, 673 iguideUpdateAgentBanner: async function (nUserID, bannerId, bannerName) { 674 return await api.postAnnotatedSql(`vendors/iguide/clients/${nUserID}/banners`, JSON.stringify({ bannerId, bannerName })); 675 }, 676 iguideUpdateOrderBanner: async function (nIguideOrderID, bannerId, bannerName) { 677 return await api.postAnnotatedSql(`vendors/iguide/orders/${nIguideOrderID}/banners`, JSON.stringify({ bannerId, bannerName })); 678 }, 679 //iguideUpdateOrderBanner: async function (nIGuideOrderID, bannerId) { 680 // return await api.postAnnotatedSql(`vendors/iguide/orderbanner/${nIGuideOrderID}`, JSON.stringify({ bannerId })); 681 //}, 682 iguideUpdateHDUser: async function (nUserID, sIGuideUserID) { 683 return await api.postAnnotatedSql(`vendors/iguide/users`, JSON.stringify({ iguideUserId: nUserID, iguideUserExternalId: sIGuideUserID})) 684 }, 685 cubiCasaUsers: async function (nBrandID) { 686 return await api.getAnnotatedSql(`vendors/cubicasa/users`); 687 }, 688 createLocalCCOrder: async function (nSiteID) { 689 return await api.postAnnotatedSql(`vendors/cubicasa/orders`, JSON.stringify({ siteId: nSiteID })); 690 }, 691 cubiCasaActivityLog: async function (nCubiCasaOrderID) { 692 return await api.getAnnotatedSql(`vendors/cubicasa/orders/${nCubiCasaOrderID}/activity-logs`); 693 }, 694 getMarketingKitSiteData: async function (nSiteID, bIncludeV1) { 695 return await api.getAnnotatedSql(`marketing-kits/sites/${nSiteID}?includeV1=${bIncludeV1}`, null, true); 696 }, 697 getESoftProducts(nBrandID) { 698 return api.getAnnotatedSql(`vendors/esoft/products${(nBrandID ? `?brandId=${nBrandID}` : '')}`) 699 .then(data => { 700 if (data) { 701 return data.map(d => { 702 if (d.editMethod == 'AI_ONLY') { 703 d.ainame = `${d.name} (AI Powered)`; 704 d.isai = true; 705 } 706 delete d.editMethod; 707 return d; 708 }); 709 } 710 return []; 711 }); 712 }, 713 titusPreAuth: async function(orderStartGuid, orderId, successUrl, cancelUrl, estimatedAmount, brandId) { 714 return api.postAnnotatedSql(`vendors/titus/pre-auths/${brandId ?? ""}`, JSON.stringify({ orderStartGuid, orderId, successUrl, cancelUrl, estimatedAmount })); 715 }, 716 titusAuthorization: async function (id) { 717 return api.getAnnotatedSql(`vendors/titus/authorizations/${id}`, JSON.stringify({})); 718 }, 719 titusCancelAuthorization: async function (id) { 720 return api.deleteAnnotatedSql(`vendors/titus/authorizations/${id}`, JSON.stringify({})); 721 }, 722 //the payments/* routes below are v3-only (ApiVersions.THREE_UX), so they must ask for "3.0" - the "2.0" default is refused 723 titusPaymentMethodsUrl: async function () { 724 return api.getAnnotatedSql(`payments/paymentsmanagesurl`, undefined, false, "3.0"); 725 }, 726 //API-510: signed URL for Titus's add-card popup (titus.addPaymentMethod). clientId: the client the card is for 727 //when a team member orders on their behalf; omit for the signed-in user. Both this and paymentCards act for a 728 //user, so call them inside api.asUser(...) - with only a brand session the API sees the brand owner. 729 titusAddPaymentMethodUrl: async function (clientId) { 730 return api.postAnnotatedSql(clientId ? `payments/clients/${clientId}/cards/add-card-url` : `payments/clients/cards/add-card-url`, JSON.stringify({}), false, "3.0"); 731 }, 732 //{ paymentCustomerId, defaultId, found, cards: [{ id, brand, last4, expM
732onth, expYear, name, type, label }] } 733 paymentCards: async function (clientId) { 734 return api.getAnnotatedSql(clientId ? `payments/clients/${clientId}/cards` : `payments/clients/cards`, undefined, false, "3.0"); 735 }, 736 //API-613: Titus saves every card its popup adds, even one already on file. { cardId, deduplicated }: the card the 737 //page should use - the original when the new one was an exact duplicate (and has been removed). Inside api.asUser. 738 titusDeduplicateCard: async function (clientId, cardId) { 739 return api.postAnnotatedSql(clientId ? `payments/clients/${clientId}/cards/${encodeURIComponent(cardId)}/deduplications` : `payments/clients/cards/${encodeURIComponent(cardId)}/deduplications`, JSON.stringify({}), false, "3.0"); 740 }, 741 //API-613: a logged-out cart has no user to save a Titus card to, so it makes a guest Titus customer under the brand 742 //session (bsid) - never the brand owner's own customer. order.asp claims it for the user it creates or matches. 743 //{ customerId, url }: the guest customer and the signed URL for Titus's add-card popup 744 titusGuestCustomer: async function (name, email) { 745 return api.postAnnotatedSql(`vendors/titus/guest-customers`, JSON.stringify({ name, email })); 746 }, 747 //{ url }: another add-card popup on the same guest customer (a second card, or a retry) 748 titusGuestAddCardUrl: async function (customerId) { 749 return api.postAnnotatedSql(`vendors/titus/guest-customers/${encodeURIComponent(customerId)}/add-card-url`, JSON.stringify({})); 750 }, 751 //the guest customer's cards, the same shape as paymentCards 752 titusGuestCards: async function (customerId) { 753 return api.getAnnotatedSql(`vendors/titus/guest-customers/${encodeURIComponent(customerId)}/cards`); 754 } 755};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.