Last active
February 24, 2025 07:24
-
-
Save dlh3/7e18fec42cc7c43d51e93a6d2bcac6fb to your computer and use it in GitHub Desktop.
Costco Receipts Extractor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
////////////////////// | |
// QUERIES | |
////////////////////// | |
const Query = (function() { | |
const receiptsWithCounts = { | |
name: 'receiptsWithCounts', | |
query: `{"query":"query | |
receiptsWithCounts($startDate: String!, $endDate: String!, $documentType: String!, $documentSubType: String!) { | |
receiptsWithCounts(startDate: $startDate, endDate: $endDate, documentType: $documentType, documentSubType: $documentSubType) { | |
receipts { | |
warehouseName | |
receiptType | |
documentType | |
transactionDateTime | |
transactionDate | |
companyNumber | |
warehouseNumber | |
operatorNumber | |
warehouseName | |
warehouseShortName | |
registerNumber | |
transactionNumber | |
transactionType | |
transactionBarcode | |
total | |
warehouseAddress1 | |
warehouseAddress2 | |
warehouseCity | |
warehouseState | |
warehouseCountry | |
warehousePostalCode | |
totalItemCount | |
subTotal | |
taxes | |
total | |
invoiceNumber | |
sequenceNumber | |
itemArray { | |
itemNumber | |
itemDescription01 | |
unit | |
amount | |
taxFlag | |
fuelUnitQuantity | |
itemUnitPriceAmount | |
fuelUomCode | |
${/* | |
frenchItemDescription1 | |
itemDescription02 | |
frenchItemDescription2 | |
itemIdentifier | |
itemDepartmentNumber | |
merchantID | |
entryMethod | |
transDepartmentNumber | |
fuelGradeCode | |
fuelUomDescription | |
fuelUomDescriptionFr | |
fuelGradeDescription | |
fuelGradeDescriptionFr | |
*/''} | |
} | |
tenderArray { | |
tenderTypeCode | |
tenderSubTypeCode | |
tenderDescription | |
amountTender | |
displayAccountNumber | |
sequenceNumber | |
approvalNumber | |
responseCode | |
tenderTypeName | |
transactionID | |
merchantID | |
entryMethod | |
tenderAcctTxnNumber | |
tenderAuthorizationCode | |
tenderTypeName | |
tenderTypeNameFr | |
tenderEntryMethodDescription | |
} | |
subTaxes { | |
tax1 | |
tax2 | |
tax3 | |
tax4 | |
aTaxPercent | |
aTaxLegend | |
aTaxAmount | |
aTaxPrintCode | |
aTaxPrintCodeFR | |
aTaxIdentifierCode | |
bTaxPercent | |
bTaxLegend | |
bTaxAmount | |
bTaxPrintCode | |
bTaxPrintCodeFR | |
bTaxIdentifierCode | |
cTaxPercent | |
cTaxLegend | |
cTaxAmount | |
cTaxIdentifierCode | |
dTaxPercent | |
dTaxLegend | |
dTaxAmount | |
dTaxPrintCode | |
dTaxPrintCodeFR | |
dTaxIdentifierCode | |
uTaxLegend | |
uTaxAmount | |
uTaxableAmount | |
} | |
instantSavings | |
membershipNumber | |
} | |
} | |
}","variables":{"startDate":"1/01/2024","endDate":"12/31/2024","documentType":"all","documentSubType":"all"}}` | |
}; | |
const getOnlineOrders = { | |
name: 'getOnlineOrders', | |
query: `{"query":"query | |
getOnlineOrders($startDate: String!, $endDate: String!, $pageNumber: Int , $pageSize: Int, $warehouseNumber: String!) { | |
getOnlineOrders(startDate: $startDate, endDate: $endDate, pageNumber: $pageNumber, pageSize: $pageSize, warehouseNumber: $warehouseNumber) { | |
pageNumber | |
pageSize | |
totalNumberOfRecords | |
bcOrders { | |
orderHeaderId | |
orderPlacedDate : orderedDate | |
orderNumber : sourceOrderNumber | |
orderTotal | |
warehouseNumber | |
status | |
emailAddress | |
orderCancelAllowed | |
orderPaymentFailed : orderPaymentEditAllowed | |
orderReturnAllowed | |
orderLineItems { | |
orderLineItemCancelAllowed | |
orderLineItemId | |
orderReturnAllowed | |
itemId | |
itemNumber | |
itemTypeId | |
lineNumber | |
itemDescription | |
deliveryDate | |
warehouseNumber | |
status | |
orderStatus | |
parentOrderLineItemId | |
isFSAEligible | |
shippingType | |
shippingTimeFrame | |
isShipToWarehouse | |
carrierItemCategory | |
carrierContactPhone | |
programTypeId | |
isBuyAgainEligible | |
scheduledDeliveryDate | |
scheduledDeliveryDateEnd | |
configuredItemData | |
shipment { | |
shipmentId | |
orderHeaderId | |
orderShipToId | |
lineNumber | |
orderNumber | |
shippingType | |
shippingTimeFrame | |
shippedDate | |
packageNumber | |
trackingNumber | |
trackingSiteUrl | |
carrierName | |
estimatedArrivalDate | |
deliveredDate | |
isDeliveryDelayed | |
isEstimatedArrivalDateEligible | |
statusTypeId | |
status | |
pickUpReadyDate | |
pickUpCompletedDate | |
reasonCode | |
trackingEvent { | |
event | |
carrierName | |
eventDate | |
estimatedDeliveryDate | |
scheduledDeliveryDate | |
trackingNumber | |
} | |
} | |
} | |
} | |
} | |
}","variables":{"pageNumber":1,"pageSize":10,"startDate":"2024-1-01","endDate":"2024-12-31","warehouseNumber":"894"}}` | |
}; | |
const getOrderDetails = { | |
name: 'getOrderDetails', | |
query: `{"query":"query | |
getOrderDetails($orderNumbers: [String]) { | |
getOrderDetails(orderNumbers:$orderNumbers) { | |
warehouseNumber | |
orderNumber : sourceOrderNumber | |
orderPlacedDate : orderedDate | |
status | |
locale | |
orderReturnAllowed | |
shopCardAppliedAmount | |
giftOfMembershipAppliedAmount | |
orderCancelAllowed | |
orderPaymentFailed : orderPaymentEditAllowed | |
orderShippingEditAllowed | |
merchandiseTotal | |
retailDeliveryFee | |
shippingAndHandling | |
grocerySurcharge | |
frozenSurchargeFee | |
uSTaxTotal1 | |
foreignTaxTotal1 | |
foreignTaxTotal2 | |
foreignTaxTotal3 | |
foreignTaxTotal4 | |
orderTotal | |
firstName | |
lastName | |
line1 | |
line2 | |
line3 | |
city | |
state | |
postalCode | |
countryCode | |
companyName | |
emailAddress | |
phoneNumber | |
membershipNumber | |
nonMemberSurchargeAmount | |
discountAmount | |
retailDeliveryFees { | |
key | |
value | |
} | |
orderPayment { | |
paymentType | |
totalCharged | |
cardExpireMonth | |
cardExpireYear | |
nameOnCard | |
cardNumber | |
isGOMPayment | |
storedValueBucket | |
} | |
shipToAddress : orderShipTos { | |
referenceNumber | |
firstName | |
lastName | |
line1 | |
line2 | |
line3 | |
city | |
state | |
postalCode | |
countryCode | |
companyName | |
emailAddress | |
phoneNumber : contactPhone | |
isShipToWarehouse | |
addressWarehouseName | |
giftMessage | |
giftToFirstName | |
giftToLastName | |
giftFromName | |
orderLineItems { | |
shipToWarehousePackageStatus | |
orderStatus | |
orderNumber | |
orderedDate | |
itemTypeId | |
isFeeItem | |
orderLineItemCancelAllowed | |
estimatedDeliveryDate | |
supplierAvailabilityDate | |
fulfilledBy | |
itemNumber | |
itemDescription : sourceItemDescription | |
price : unitPrice | |
quantity : orderedTotalQuantity | |
merchandiseTotalAmount | |
lineItemId | |
sourceLineItemId | |
parentOrderLineItemId | |
itemId | |
isBuyAgainEligible | |
sequenceNumber : sourceSequenceNumber | |
parentOrderNumber | |
lineNumber | |
itemTypeId | |
replaceStatus | |
returnType | |
itemType | |
programType | |
minOrderDate | |
maxOrderDate | |
fSADescription | |
odsJobId | |
orderedShipMethodDescription | |
shippingChargeAmount | |
preferredArrivalDate | |
requestedDeliveryDate | |
returnStatus | |
productSerialNumber | |
configuredItemData | |
orderedShipMethod | |
isRescheduleEligible | |
deliveryReschedulingSite | |
scheduledDeliveryDate | |
scheduledDeliveryDateEnd | |
limitedReturnPolicyRule | |
isLimitedReturnPolicyExceeded | |
itemWeight | |
itemGroupNumber | |
isPerishable | |
carrierItemCategory | |
carrierContactPhone | |
isUPSMILabelEligible | |
parentLineNumber | |
isExchangeBlock | |
shipToAddressReferenceNumber | |
isVerificationRequired | |
isDept24 | |
returnableQuantity | |
totalReturnedQuantity | |
exchangeOrderNumber | |
returnType | |
isGiftMessageSupported | |
isReturnCalendarEligible | |
programTypeId | |
bundleParentNumber, freightSavings, freightAdditionalSavings | |
configuredItemData | |
itemStatus { | |
orderPlaced { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
shipped { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
cancelled { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
returned { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
delivered { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
cancellationRequested { | |
quantity | |
transactionDate | |
orderLineItemId | |
lineItemStatusId | |
orderLineItemCancelAllowed | |
orderLineItemReturnAllowed | |
} | |
} | |
shipment { | |
lineNumber | |
orderNumber | |
packageNumber | |
trackingNumber | |
pickUpCompletedDate | |
pickUpReadyDate | |
carrierName | |
trackingSiteUrl | |
shippedDate | |
estimatedArrivalDate | |
deliveredDate | |
isDeliveryDelayed | |
isEstimatedArrivalDateEligible | |
reasonCode | |
trackingEvent { | |
event | |
carrierName | |
eventDate | |
estimatedDeliveryDate | |
scheduledDeliveryDate | |
trackingNumber | |
} | |
} | |
} | |
} | |
} | |
}","variables":{"orderNumbers":["1234567890"]}}` | |
}; | |
return {receiptsWithCounts, getOnlineOrders, getOrderDetails}; | |
})(); | |
////////////////////// | |
// XHR | |
////////////////////// | |
const queryCostcoOrders = (query) => fetch("https://ecom-api.costco.com/ebusiness/order/v1/orders/graphql", { | |
"headers": { | |
"accept": "*/*", | |
"client-identifier": "481b1aec-aa3b-454b-b81b-48187e28f205", | |
"content-type": "application/json-patch+json", | |
"costco-x-authorization": `Bearer ${localStorage.idToken}`, | |
"costco-x-wcs-clientid": localStorage.clientID, | |
"costco.env": "ecom", | |
"costco.service": "restOrders", | |
}, | |
"referrer": "https://www.costco.ca/", | |
"referrerPolicy": "strict-origin-when-cross-origin", | |
"body": query.query.replaceAll(/\s+/g, ' '), | |
"method": "POST", | |
"mode": "cors", | |
"credentials": "omit" | |
}) | |
.then(r => r.json()) | |
.then(json => json.data[query.name]); | |
// create functions for each query | |
Object.keys(Query) | |
.forEach(queryName => | |
window[queryName] = () => queryCostcoOrders(Query[queryName])); | |
////////////////////// | |
// PARSING | |
////////////////////// | |
let receiptsJson = await receiptsWithCounts(); | |
let receipts = receiptsJson.receipts; | |
let items = receipts | |
.flatMap(receipt => receipt.itemArray) | |
.map(({fuelUnitQuantity, unit, ...item}) => | |
(item.unit = item.fuelUomCode ? fuelUnitQuantity : unit, | |
item)); | |
////////////////////// | |
// UTILITY FUNCTIONS | |
////////////////////// | |
const logReceiptItems = () => | |
console.table(items); | |
const getReceiptsTotal = () => | |
receipts.reduce((sum, {total}) => sum += total, 0); | |
////////////////////// | |
// SUMMARY | |
////////////////////// | |
console.log(`Supported query functions: ${Object.keys(Query).join(', ')}`); | |
console.log(`Loaded ${receipts.length} receipts with ${items.length} items`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment