11// WooCommerce - WooCommerce Orders Retrieve to Google Sheets
2- // Last update: 2025-08-31
2+ // Last update: 2025-09-01
33
44
55// https://script.google.com > New project >
88// -- Choose which function to run: WooCommerceOrdersRetrieve
99// -- Choose which deployment should run: Head
1010// -- Select event source: Time-driven
11- // -- Select type of time based trigger: Minutes timer
12- // -- Select minute interval: Every 30 minutes
11+ // -- Select type of time based trigger: Hour timer
12+ // -- Select minute interval: Every 12 hours
1313
1414
1515// Google Sheets Query to aggregate orders from line/item level to order level
16- // =QUERY('Orders Line Level'!A:AY, "SELECT A, MAX(B), MAX(C), MAX(D), MAX(E), MAX(F), MAX(G), MAX(H), MAX(I), MAX(J), MAX(K), MAX(L), MAX(M), MAX(N), MAX(O), MAX(P), MAX(Q), MAX(R), MAX(S), MAX(T), MAX(U), MAX(V), MAX(W), MAX(X), MAX(Y), MAX(Z), MAX(AA), MAX(AB), MAX(AC), MAX(AD), MAX(AE), MAX(AF), MAX(AG), MAX(AH), MAX(AI), MAX(AJ), MAX(AK), MAX(AL), MAX(AM), MAX(AN), MAX(AO), MAX(AP), MAX(AQ), MAX(AR), MAX(AS), MAX(AT), MAX(AU), MAX(AV) WHERE A IS NOT NULL GROUP BY A LABEL A 'order_id', MAX(B) 'created_via', MAX(C) 'status', MAX(D) 'currency', MAX(E) 'prices_include_tax', MAX(F) 'customer_id', MAX(G) 'customer_user_agent', MAX(H) 'customer_note', MAX(I) 'payment_method', MAX(J) 'payment_method_title', MAX(K) 'language_code', MAX(L) 'billing_first_name', MAX(M) 'billing_last_name', MAX(N) 'billing_company', MAX(O) 'billing_address_1', MAX(P) 'billing_address_2', MAX(Q) 'billing_city', MAX(R) 'billing_state', MAX(S) 'billing_postcode', MAX(T) 'billing_country', MAX(U) 'billing_email', MAX(V) 'billing_phone', MAX(W) 'shipping_first_name', MAX(X) 'shipping_last_name', MAX(Y) 'shipping_company', MAX(Z) 'shipping_address_1', MAX(AA) 'shipping_address_2', MAX(AB) 'shipping_city', MAX(AC) 'shipping_state', MAX(AD) 'shipping_postcode', MAX(AE) 'shipping_country', MAX(AF) 'shipping_methods', MAX(AG) 'date_created', MAX(AH) 'date_created_gmt', MAX(AI) 'date_modified', MAX(AJ) 'date_modified_gmt', MAX(AK) 'date_paid', MAX(AL) 'date_paid_gmt', MAX(AM) 'date_completed', MAX(AN) 'date_completed_gmt', MAX(AO) 'discount_tax', MAX(AP) 'discount_total', MAX(AQ) 'shipping_tax', MAX(AR) 'shipping_total', MAX(AS) 'cart_tax', MAX(AT) 'cart', MAX(AU) 'transaction_fee', MAX(AV) 'total_tax' ", 1)
16+ // =QUERY('sales_orders_articles'!A:BD, "SELECT MAX(A), MAX(B), MAX(C), MAX(D), MAX(E), MAX(F), MAX(G), MAX(H), MAX(I), MAX(J), MAX(K), MAX(L), MAX(M), MAX(N), MAX(O), MAX(P), MAX(Q), MAX(R), MAX(S), MAX(T), MAX(U), MAX(V), MAX(W), MAX(X), MAX(Y), MAX(Z), MAX(AA), MAX(AB), MAX(AC), MAX(AD), MAX(AE), MAX(AF), MAX(AG), MAX(AH), MAX(AI), MAX(AJ), MAX(AK), MAX(AL), MAX(AM), MAX(AN), MAX(AO), MAX(AP), MAX(AQ), SUM(AR), SUM(AS), SUM(AT), SUM(AU), SUM(AV), SUM(AW), SUM(AX), SUM(AY), SUM(AZ), SUM(BA), SUM(BB), SUM(BC), SUM(BD) WHERE A IS NOT NULL GROUP BY A, B LABEL MAX(A) 'source_system', MAX(B) 'order_id', MAX(C) 'created_via', MAX(D) 'status', MAX(E) 'date_created', MAX(F) 'date_created_gmt', MAX(G) 'date_modified', MAX(H) 'date_modified_gmt', MAX(I) 'date_paid', MAX(J) 'date_paid_gmt', MAX(K) 'date_completed', MAX(L) 'date_completed_gmt', MAX(M) 'currency', MAX(N) 'prices_include_tax', MAX(O) 'order_attribution_origin', MAX(P) 'order_attribution_device_type', MAX(Q) 'customer_id', MAX(R) 'customer_user_agent', MAX(S) 'customer_note', MAX(T) 'payment_method', MAX(U) 'payment_method_title', MAX(V) 'language_code', MAX(W) 'billing_first_name', MAX(X) 'billing_last_name', MAX(Y) 'billing_company', MAX(Z) 'billing_address_1', MAX(AA) 'billing_address_2', MAX(AB) 'billing_city', MAX(AC) 'billing_state', MAX(AD) 'billing_postcode', MAX(AE) 'billing_country', MAX(AF) 'billing_email', MAX(AG) 'billing_phone', MAX(AH) 'shipping_first_name', MAX(AI) 'shipping_last_name', MAX(AJ) 'shipping_company', MAX(AK) 'shipping_address_1', MAX(AL) 'shipping_address_2', MAX(AM) 'shipping_city', MAX(AN) 'shipping_state', MAX(AO) 'shipping_postcode', MAX(AP) 'shipping_country', MAX(AQ) 'shipping_methods', SUM(AR) 'discount_amount_net', SUM(AS) 'discount_amount_tax', SUM(AT) 'discount_amount_gross', SUM(AU) 'shipping_amount_net', SUM(AV) 'shipping_amount_tax', SUM(AW) 'shipping_amount_gross', SUM(AX) 'items_amount_net', SUM(AY) 'items_amount_tax', SUM(AZ) 'items_amount_gross', SUM(BA) 'transaction_fee', SUM(BB) 'refund_amount_net', SUM(BC) 'refund_amount_tax', SUM(BD) 'refund_amount_gross'", 1)
1717
1818
1919function WooCommerceOrdersRetrieve ( ) {
2020 const options = {
2121 site_url : "https://website.com" ,
2222 consumer_key : "ck_xxxxxxxx" ,
2323 consumer_secret : "cs_xxxxxxxx" ,
24- output_sheet_name : "Orders Line Level " ,
24+ output_sheet_name : "sales_orders_articles " ,
2525 test_mode : false ,
2626 last_fetch_date_reset : false ,
2727 } ;
@@ -40,7 +40,7 @@ function WooCommerceOrdersRetrieve() {
4040 // Get last fetch date to fetch only new orders
4141 let lastFetchDate = PropertiesService . getScriptProperties ( ) . getProperty ( "SETTINGS_LAST_SYNC" ) ;
4242 if ( lastFetchDate ) lastFetchDate = lastFetchDate . slice ( 0 , lastFetchDate . lastIndexOf ( '.' ) ) + 'Z' ;
43- Logger . log ( "The last fetch date is: " + lastFetchDate ) ;
43+ Logger . log ( "The last imported modified date is: " + lastFetchDate ) ;
4444
4545 // Reusable API fetch function
4646 const fetchWooCommerceAPI = ( endpoint , params = { } ) => {
@@ -93,7 +93,7 @@ function WooCommerceOrdersRetrieve() {
9393
9494 // Fetch paginated orders
9595 while ( true ) {
96- const params = { per_page : perPage , page : page , orderby : "date" , order : "asc" , orderby : "modified" } ;
96+ const params = { per_page : perPage , page : page , orderby : "date" , order : "asc" } ;
9797 if ( lastFetchDate ) {
9898 params . modified_after = lastFetchDate ;
9999 }
@@ -120,16 +120,26 @@ function WooCommerceOrdersRetrieve() {
120120 // If sheet is empty, add headers
121121 if ( sheet . getLastRow ( ) === 0 ) {
122122 const headers = [
123- // Order core fields
123+ "source_system" ,
124124 "order_id" ,
125125 // "parent_id",
126126 // "number",
127127 // "order_key",
128128 "created_via" ,
129129 // "version",
130130 "status" ,
131+ "date_created" ,
132+ "date_created_gmt" ,
133+ "date_modified" ,
134+ "date_modified_gmt" ,
135+ "date_paid" ,
136+ "date_paid_gmt" ,
137+ "date_completed" ,
138+ "date_completed_gmt" ,
131139 "currency" ,
132140 "prices_include_tax" ,
141+ "order_attribution_origin" ,
142+ "order_attribution_device_type" ,
133143 "customer_id" ,
134144 // "customer_ip_address",
135145 "customer_user_agent" ,
@@ -166,38 +176,31 @@ function WooCommerceOrdersRetrieve() {
166176 "shipping_postcode" ,
167177 "shipping_country" ,
168178 "shipping_methods" ,
169- "date_created" ,
170- "date_created_gmt" ,
171- "date_modified" ,
172- "date_modified_gmt" ,
173- "date_paid" ,
174- "date_paid_gmt" ,
175- "date_completed" ,
176- "date_completed_gmt" ,
177- "discount_tax" ,
178- "discount_total" ,
179- "shipping_tax" ,
180- "shipping_total" ,
181- "cart_tax" ,
182- "cart" ,
179+ "discount_amount_net" ,
180+ "discount_amount_tax" ,
181+ "discount_amount_gross" ,
182+ "shipping_amount_net" ,
183+ "shipping_amount_tax" ,
184+ "shipping_amount_gross" ,
185+ "items_amount_net" ,
186+ "items_amount_tax" ,
187+ "items_amount_gross" ,
183188 "transaction_fee" ,
184- "total_tax" ,
185- "total" ,
186- "refund_tax" ,
187- "refund" ,
189+ "refund_amount_net" ,
190+ "refund_amount_tax" ,
191+ "refund_amount_gross" ,
188192 "item_id" ,
189193 "item_name" ,
190194 // "product_id",
191195 "sku" ,
192196 "variation_id" ,
193197 "variation_name" ,
194- "price" ,
195- "quantity" ,
196198 "tax_class" ,
197- "subtotal_tax" ,
198- "subtotal" ,
199- "line_total_tax" ,
200- "line_total" ,
199+ "quantity" ,
200+ "price_gross" ,
201+ "line_item_amount_net" ,
202+ "line_item_amount_tax" ,
203+ "line_item_amount_gross" ,
201204 // "taxes",
202205 // "line_meta_data",
203206 ] ;
@@ -211,12 +214,13 @@ function WooCommerceOrdersRetrieve() {
211214 values . forEach ( ( row , i ) => {
212215 if ( row [ 0 ] ) {
213216 if ( ! existing [ row [ 0 ] ] ) existing [ row [ 0 ] ] = [ ] ;
214- existing [ row [ 0 ] ] . push ( i + 2 ) ; // store row numbers
217+ existing [ row [ 0 ] ] . push ( i + 2 ) ; // Store row numbers
215218 }
216219 } ) ;
217220 }
218221
219222 const fieldMap = [
223+ order => "WooCommerce" ,
220224 order => order . id ,
221225 // order => order.parent_id,
222226 // order => order.number,
@@ -230,8 +234,24 @@ function WooCommerceOrdersRetrieve() {
230234 }
231235 return status ;
232236 } ,
237+ order => order . date_created ,
238+ order => order . date_created_gmt ,
239+ order => order . date_modified ,
240+ order => order . date_modified_gmt ,
241+ order => order . date_paid ,
242+ order => order . date_paid_gmt ,
243+ order => order . date_completed ,
244+ order => order . date_completed_gmt ,
233245 order => order . currency ,
234246 order => order . prices_include_tax ,
247+ order => {
248+ let source = ( order . meta_data ?. find ( m => m . key === "_wc_order_attribution_source_type" ) || { } ) . value || "" ;
249+ if ( source === "typein" ) {
250+ return "Direct" ;
251+ }
252+ return source . charAt ( 0 ) . toUpperCase ( ) + source . slice ( 1 ) ;
253+ } ,
254+ order => ( order . meta_data ?. find ( m => m . key === "_wc_order_attribution_device_type" ) || { } ) . value || "" ,
235255 order => order . customer_id === 0 ? "" : order . customer_id ,
236256 // order => order.customer_ip_address,
237257 order => order . customer_user_agent ,
@@ -270,29 +290,23 @@ function WooCommerceOrdersRetrieve() {
270290 order => order . shipping_lines && order . shipping_lines . length > 0
271291 ? order . shipping_lines . map ( sl => woocommerceShippingMethods [ sl . method_id ] || sl . method_title ) . join ( ", " )
272292 : "" ,
273- order => order . date_created ,
274- order => order . date_created_gmt ,
275- order => order . date_modified ,
276- order => order . date_modified_gmt ,
277- order => order . date_paid ,
278- order => order . date_paid_gmt ,
279- order => order . date_completed ,
280- order => order . date_completed_gmt ,
281- order => order . discount_tax ,
282293 order => order . discount_total ,
283- order => order . shipping_tax ,
294+ order => order . discount_tax ,
295+ order => parseFloat ( order . discount_total || 0 ) + parseFloat ( order . discount_tax || 0 ) ,
284296 order => order . shipping_total ,
285- order => order . cart_tax ,
286- order => parseFloat ( order . total || 0 ) - parseFloat ( order . shipping_total || 0 ) ,
297+ order => order . shipping_tax ,
298+ order => parseFloat ( order . shipping_total || 0 ) + parseFloat ( order . shipping_tax || 0 ) ,
299+ order => order . line_items . reduce ( ( sum , item ) => sum + parseFloat ( item . total || 0 ) , 0 ) ,
300+ order => order . line_items . reduce ( ( sum , item ) => sum + parseFloat ( item . total_tax || 0 ) , 0 ) ,
301+ order => order . line_items . reduce ( ( sum , item ) => sum + parseFloat ( item . total || 0 ) + parseFloat ( item . total_tax || 0 ) , 0 ) ,
287302 order => {
288303 const paypalFee = order . meta_data ? order . meta_data . find ( m => m . key === 'PayPal Transaction Fee' ) : null ;
289304 const stripeFee = order . meta_data ? order . meta_data . find ( m => m . key === '_stripe_fee' ) : null ;
290305 return paypalFee ? paypalFee . value : ( stripeFee ? stripeFee . value : null ) ;
291306 } ,
292- order => order . total_tax ,
293- order => order . total ,
294- order => order . refunds && order . refunds . length > 0 ? order . refunds . reduce ( ( sum , r ) => sum + Math . abs ( parseFloat ( r . total_tax || 0 ) ) , 0 ) : 0 ,
295307 order => order . refunds && order . refunds . length > 0 ? order . refunds . reduce ( ( sum , r ) => sum + Math . abs ( parseFloat ( r . total || 0 ) ) , 0 ) : 0 ,
308+ order => order . refunds && order . refunds . length > 0 ? order . refunds . reduce ( ( sum , r ) => sum + Math . abs ( parseFloat ( r . total_tax || 0 ) ) , 0 ) : 0 ,
309+ order => order . refunds && order . refunds . length > 0 ? order . refunds . reduce ( ( sum , r ) => sum + Math . abs ( parseFloat ( r . total || 0 ) + parseFloat ( r . total_tax || 0 ) ) , 0 ) : 0 ,
296310 ( order , item ) => item . id ,
297311 ( order , item ) => item . name ,
298312 // (order, item) => item.product_id,
@@ -301,34 +315,52 @@ function WooCommerceOrdersRetrieve() {
301315 ( order , item ) => item . meta_data && item . meta_data . length > 0
302316 ? item . meta_data . filter ( m => m . key && m . key . indexOf ( "pa_" ) === 0 ) . map ( m => m . display_key + ": " + m . display_value ) . join ( ", " )
303317 : "" ,
304- ( order , item ) => parseFloat ( item . total || 0 ) / parseFloat ( item . quantity || 1 ) ,
305- ( order , item ) => item . quantity ,
306318 ( order , item ) => woocommerceTaxRates [ item . tax_class ] || ( item . tax_class === "" ? woocommerceTaxRates [ "standard" ] : "" ) ,
307- ( order , item ) => item . subtotal_tax ,
308- ( order , item ) => item . subtotal ,
309- ( order , item ) => item . total_tax ,
319+ ( order , item ) => item . quantity ,
320+ ( order , item ) => parseFloat ( item . price || 0 ) . toFixed ( 2 ) ,
310321 ( order , item ) => item . total ,
322+ ( order , item ) => item . total_tax ,
323+ ( order , item ) => parseFloat ( item . total || 0 ) + parseFloat ( item . total_tax || 0 ) ,
311324 // (order, item) => JSON.stringify(item.taxes || []),
312325 // (order, item) => JSON.stringify(item.meta_data || [])
313326 ] ;
314327
315- // Map and write orders
328+ // Collect all rows to delete (for orders that already exist on the sheet), and also prepare the new rows to append
316329 const rows = [ ] ;
330+ const rowsToDeleteAll = [ ] ;
331+
317332 orders . forEach ( order => {
318333 if ( ! order . line_items || order . line_items . length === 0 ) return ;
319- // Remove old rows for this order_id
334+
335+ // If the order already exists in the sheet, mark its rows for deletion
320336 if ( existing [ order . id ] ) {
321- const rowsToDelete = existing [ order . id ] ;
322- // Delete from bottom to top (to not mess indices)
323- rowsToDelete . sort ( ( a , b ) => b - a ) . forEach ( r => sheet . deleteRow ( r ) ) ;
337+ rowsToDeleteAll . push ( ...existing [ order . id ] ) ;
324338 }
325339
340+ // Build new rows for this order's line items
326341 order . line_items . forEach ( item => {
327342 const row = fieldMap . map ( fieldFn => fieldFn ( order , item ) ) ;
328343 rows . push ( row ) ;
329344 } ) ;
330345 } ) ;
331346
347+ // Remove duplicates, sort descending and delete
348+ if ( rowsToDeleteAll . length > 0 ) {
349+ const uniqueRows = Array . from ( new Set ( rowsToDeleteAll ) ) ;
350+ uniqueRows . sort ( ( a , b ) => b - a ) ; // Descending
351+
352+ // Delete each row if it's still within the current sheet bounds
353+ for ( let i = 0 ; i < uniqueRows . length ; i ++ ) {
354+ const r = uniqueRows [ i ] ;
355+ // Safety guard to avoid out-of-bounds
356+ if ( r >= 1 && r <= sheet . getLastRow ( ) ) {
357+ sheet . deleteRow ( r ) ;
358+ } else {
359+ Logger . log ( "Skipped deleting out-of-bounds row: " + r ) ;
360+ }
361+ }
362+ }
363+
332364 // Batch append rows for efficiency
333365 if ( rows . length > 0 ) {
334366 sheet . getRange ( sheet . getLastRow ( ) + 1 , 1 , rows . length , rows [ 0 ] . length ) . setValues ( rows ) ;
0 commit comments