Skip to content

Instantly share code, notes, and snippets.

@HaruKawamata
Created March 6, 2025 05:10
Show Gist options
  • Save HaruKawamata/c65712dcbe6af9ba17c949afcc39b824 to your computer and use it in GitHub Desktop.
Save HaruKawamata/c65712dcbe6af9ba17c949afcc39b824 to your computer and use it in GitHub Desktop.
Square Data Stream Integration
# typed: true
# frozen_string_literal: true
module DataStreamSyncer
class Square < ::DataStreamSyncer::Base
# the square syncer should run every SYNC_FREQUENCY minutes
# this overrides DSS::Base
def should_sync_at?(_time)
true
end
# query square for the last 2 hours of data
# this overrides DSS::Base
def sync_options_logic(tz_time)
{ date_from: tz_time.advance(hours: -2), date_to: tz_time }
end
def hashed_data(options)
TimeZone.in(time_zone_for_data_stream) do
@options = options
data = []
grouped_data = group_data(
square_orders(options), proc do |order|
order["created_at"] = Time.zone.parse(order["created_at"])
order["created_at"].to_f
end
).compact
grouped_data.each do |time, orders|
order_lines = orders.pluck("line_items").flatten.compact
next if orders.blank?
num_orders = order_lines.length
cost = order_lines.sum do |order|
order.dig("total_money", "amount") || 0
end / 100.0 # cents --> dollars
tips = orders.sum do |order|
order.dig("total_tip_money", "amount") || 0
end / 100.0 # cents --> dollars
tax_amount = order_lines.sum do |order|
order.dig("total_tax_money", "amount") || 0
end / 100.0 # cents --> dollars
data << { time: time, stat: cost, stat_type: :sales }
data << { time: time, stat: -tax_amount, stat_type: :sales } unless @data_stream.payroll_integration&.include_tax?
data << { time: time, stat: tips, stat_type: :tips }
data << { time: time, stat: num_orders, stat_type: :transactions }
end
data
end
end
private
def square_orders(options)
return [] unless @data_stream.payroll_integration_id
matching_pi = @data_stream.payroll_integration
return [] if matching_pi.nil? || matching_pi.auth_token.blank?
client = ::Square::Client.new(matching_pi)
# For more info on handlers:
# https://github.com/ooyala/retries#handlers
handler = proc do |exception, _attempt_number, _|
error_msg = exception.response&.parsed&.fetch("message")
token_expire_msg = "This access token expired too long ago to renew. The associated merchant must reauthorize your application."
invalid_token_msg = "Invalid refresh token"
if error_msg == token_expire_msg
Rails.logger.info("(#{@data_stream.organisation.name}) - Square POS - #{token_expire_msg}")
matching_pi.update!(auth_token: nil)
elsif error_msg == invalid_token_msg
Rails.logger.info("(#{@data_stream.organisation.name}) - Square POS - #{invalid_token_msg}")
matching_pi.update!(auth_token: nil)
end
[]
end
with_retries(max_tries: MAX_ATTEMPTS, handler: handler, rescue: OAuth2::Error) do |attempt|
if attempt >= MAX_ATTEMPTS
refreshed_auth_token = ::Square::AccessTokenGenerator.new(matching_pi).get_access_token
matching_pi.auth_token = refreshed_auth_token
matching_pi.save!
return cache_failed_attempt
end
clear_failed_attempt
client.orders(@data_stream.section_identifier, options[:date_from], options[:date_to])
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment