Files
kontextwerk/api/hooks/lib/bigcommerceRestAPI.js
2025-10-02 08:54:03 +02:00

920 lines
25 KiB
JavaScript

const { bigcommerceApiOAuth, bigcommerceStoreHash, channelId } = require("../config.js")
/**
*
* @param {number} status
*/
function statusIsValid(status) {
return !!status && status < 400
}
/**
*
* @param {number} orderId
* @returns {V2OrderProductsResponseBase[]}
*/
function getOrdersProducts(orderId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/products`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json()
}
/**
* @param {number} orderId∂
* @returns {V2OrderResponseBase}
*/
function getOrderById(orderId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
/**
* @type {V2OrderResponseBase}
*/
const order = response.body.json()
order.productObjs = getOrdersProducts(orderId)
order.shipping_addressObjs = getOrderShippingAddressesById(String(orderId))
return order
}
/**
*
* @param {number} orderId
* @param {V2OrderResponseBase} order
* @returns
*/
function updateOrderById(orderId, order) {
delete order.id
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify(order),
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json()
}
/**
* @param {number} customerId
*/
function getOrdersForCustomer(customerId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders?customer_id=${customerId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
/**
* @type {V2OrderResponseBase[]}
*/
const orders = response.body.json()
orders.forEach((order) => {
order.productObjs = getOrdersProducts(order.id)
order.shipping_addressObjs = getOrderShippingAddressesById(String(order.id))
})
// every order has date_created field, so we can sort by it (latest first)
orders.sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime())
return orders
}
/**
* @returns {V2OrderProductsResponseBase[]}
*/
function getAllProducts() {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products?limit=250`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json().data
}
/**
* @param {string} [orderId]
* @returns {V2OrderShippingAddressesResponseBase[]}
*/
function getOrderShippingAddressesById(orderId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/shipping_addresses`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json()
}
/**
* @param {string} [productId]
* @returns {V2OrderProductsResponseBase}
*/
function getProductById(productId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json().data
}
/**
* @param {string} orderId
* @returns {V2OrderProductsResponseBase[]}
*/
function getOrderProductsById(orderId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/orders/${orderId}/products`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json()
}
/**
* @param {string} [productId]
* @returns {ProductImage[]}
*/
function getProductImages(productId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}/images`,
{
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
return response.body.json().data
}
/**
* @param {string} [productId]
* @param {string} [ratingId]
*/
function deleteRating(productId, ratingId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${productId}/reviews/${ratingId}`,
{
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
}
/**
* @param {string} [cartId]
* @returns {RestApiCart}
*/
function getCart(cartId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}`,
{
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText + " cart",
}
}
return response.body.json().data
}
/**
* @param {string} [cartId]
* @returns {any}
*/
function getRedirectUrl(cartId) {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/redirect_urls`,
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText + "redirect_url",
}
}
const data = response.body.json().data
return { embeddedCheckoutURL: data?.embedded_checkout_url, checkoutURL: data?.checkout_url }
}
/**
*
* @param {string} cartId
* @param { { merchandiseId: string; quantity: number; productId?: string }[]} items
*/
function addCartItem(cartId, items) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items`,
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({ line_items: items }),
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data
}
/**
*
* @param {string} cartId
* @param {{ variant_id: string;product_id: string, quantity: number;} } item
* @param {string} entityId
* @returns {any}
*/
function updateCartItem(cartId, item, entityId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items/${entityId}`,
{
method: "PUT",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({ line_item: item }),
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data
}
/**
*
* @param {string} cartId
* @param {string} entityId
* @returns {boolean}
*/
function deleteCartItem(cartId, entityId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/carts/${cartId}/items/${entityId}`,
{
method: "DELETE",
headers: {
accept: "application/json",
"content-type": "application/json",
"x-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return true
}
/**
*
* @param {number} customerId
* @param {string} email
* @returns {BigCommerceCustomer}
*/
function getCustomerById(customerId, email = "", noException = false) {
let res
if (customerId) {
res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers?id:in=${customerId}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
} else {
let encodedEmail = encodeURIComponent(email)
res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers?email:in=${encodedEmail}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
}
if (!statusIsValid(res.status)) {
if (noException) return null
throw {
status: res.status,
error: res.statusText,
}
}
if (res.body.json().data.length === 0) return null
/** @type {BigCommerceCustomer} */
const customer = res.body.json().data[0]
const formFields = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/form-field-values?customer_id=${customer.id}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(formFields.status)) {
if (noException) return null
throw {
status: formFields.status,
error: formFields.statusText,
}
}
try {
if (formFields.body && formFields.body.json()) customer.form_fields = formFields.body.json()?.data || []
} catch (e) {}
return customer
}
/**
*
* @param {number} customerId
* @param {number} addressId
* @returns {BigCommerceAddress}
*/
function getCustomerAddressById(customerId, addressId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses/?customer_id:in=${customerId}&id:in=${addressId}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data[0]
}
/**
*
* @param {number} customerId
* @param {number} addressId
*
*/
function deleteCustomerAddressById(customerId, addressId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v2/customers/${customerId}/addresses/${addressId}`,
{
method: "DELETE",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
console.log(res.status, "status")
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return true
}
/**
* @param {BigCommerceAddress} address
*/
function updateCustomerAddressById(address) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses`,
{
method: "PUT",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify([address]),
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data[0]
}
/**
*
* @param {number} customerId
* @returns {BigCommerceAddress[]}
*/
function getCustomerAddresses(customerId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses?customer_id:in=${customerId}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data
}
/**
* @param {BigCommerceAddress} address
* @returns
*/
function addCustomerAddress(address) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/addresses`,
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify([address]),
}
)
if (!statusIsValid(res.status))
throw {
status: res.status,
error: res.statusText,
}
return res.body.json().data[0]
}
/**
*
* @param {string} email
* @param {string} password
* @returns {{is_valid: boolean}}
*/
function validateCredentials(email, password) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers/validate-credentials`,
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({
email,
password,
channelId,
}),
}
)
if (res.status == 429) {
throw {
status: 429,
error: "Too many requests",
log: false,
}
}
if (res.status == 422) {
throw {
status: 422,
error: "Invalid email or password",
log: false,
}
}
if (!statusIsValid(res.status)) {
throw {
status: res.status,
error: res.statusText,
}
}
return res.body.json()
}
/**
*
* @param {Partial<BigCommerceCustomer>} customer
* @returns {BigCommerceCustomer}
*/
function updateCustomer(customer) {
const customerRes = context.http.fetch(`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/customers`, {
method: "PUT",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify([customer]),
})
if (!statusIsValid(customerRes.status))
throw {
status: customerRes.status,
error: customerRes.statusText,
}
return customerRes.body.json().data[0]
}
function generateJTI() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
}
/**
*
* @param {number} customerId
* @param {string} storeHash
* @param {string} storeUrl
* @param {string} clientId
* @param {string} clientSecret
* @param {string} [redirect_to]
* @returns
*/
function getLoginUrl(customerId, storeHash, storeUrl, clientId, clientSecret, redirect_to = "") {
const dateCreated = Math.round(new Date().getTime() / 1000)
const payload = {
iss: clientId,
iat: dateCreated,
jti: generateJTI(),
operation: "customer_login",
store_hash: storeHash,
customer_id: customerId,
redirect_to,
}
const token = context.jwt.create(payload, { secret: clientSecret, validityDuration: 600 })
return `${storeUrl}/login/token/${token}`
}
/**
* @param {number} customerId
* @returns {RestWishlist | false}
*/
function getWishlistEntries(customerId) {
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists?customer_id=${customerId}`,
{
method: "GET",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (res.status == 404) {
return false
}
if (!statusIsValid(res.status)) {
throw {
status: res.status,
error: res.statusText,
}
}
if (res.body.json().data?.length === 0) return false
return res.body.json().data[0]
}
/**
* @param {number} customerId
* @param {number} productId
* @param {number} variantId
* @returns {RestWishlist}
*/
function createWishlistEntry(productId, variantId, customerId) {
const currentWishlist = getWishlistEntries(customerId)
let res
if (currentWishlist) {
res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists/${currentWishlist?.id}/items`,
{
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({
items: [
{
product_id: Number(productId),
variant_id: Number(variantId),
},
],
}),
}
)
} else {
res = context.http.fetch(`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists`, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({
customer_id: Number(customerId),
is_public: false,
name: "Customer Wishlist",
items: [
{
product_id: Number(productId),
variant_id: Number(variantId),
},
],
}),
})
}
if (!statusIsValid(res.status)) {
throw {
status: res.status,
error: res.statusText,
}
}
return res.body.json().data
}
/**
* @param {number} customerId
* @param {number} productId
* @param {number} variantId
* @returns {boolean}
*/
function removeWishlistEntry(customerId, productId, variantId) {
const currentWishlist = getWishlistEntries(customerId)
if (!currentWishlist) {
console.log("no wishlist at all")
return true
}
const wishListEntry = currentWishlist.items.find(
(item) => item.product_id == productId && item.variant_id == variantId
)
if (!wishListEntry) {
console.log("no wishlist entry")
return true
}
const res = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/wishlists/${currentWishlist.id}/items/${wishListEntry.id}`,
{
method: "DELETE",
headers: {
accept: "application/json",
"content-type": "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
}
)
if (!statusIsValid(res.status)) {
throw {
status: res.status,
error: res.statusText,
}
}
return true
}
/**
*
* @param {number} orderId
* @returns {Partial<Order>}
*/
function createInternalOrderObject(orderId) {
const orderProducts = getOrderProductsById(String(orderId))
const bigCommerceId = orderId
const order = getOrderById(bigCommerceId)
let [internalCustomer] = context.db.find("bigCommerceCustomer", {
filter: {
bigCommerceId: order?.customer_id,
},
})
/** @type {Partial<Order>} */
const internalOrderReference = {
bigCommerceId,
customerBigCommerceId: order?.customer_id,
cost: order?.total_inc_tax,
customerTibiId: internalCustomer?.id,
status: "draft",
products: orderProducts.map((product) => {
const internalProduct = context.db.find("bigCommerceProduct", {
filter: {
bigCommerceId: product.product_id,
},
})[0]
return {
bigCommerceId: product.product_id,
tibiId: internalProduct?.id,
quantity: product.quantity,
}
}),
}
return internalOrderReference
}
module.exports = {
updateCustomer,
getOrderById,
getOrderProductsById,
getProductImages,
getProductById,
getOrderShippingAddressesById,
deleteRating,
getCart,
getRedirectUrl,
addCartItem,
deleteCartItem,
updateCartItem,
getCustomerById,
validateCredentials,
updateCustomerAddressById,
deleteCustomerAddressById,
getCustomerAddressById,
getCustomerAddresses,
addCustomerAddress,
getLoginUrl,
getOrdersForCustomer,
removeWishlistEntry,
createWishlistEntry,
getWishlistEntries,
updateOrderById,
getAllProducts,
createInternalOrderObject,
}