Created
March 6, 2025 05:10
-
-
Save HaruKawamata/c65712dcbe6af9ba17c949afcc39b824 to your computer and use it in GitHub Desktop.
Square Data Stream Integration
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
# 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