Skip to content

Commit

Permalink
Merge pull request #205 from swishjam/slack-fix-and-stripe-events
Browse files Browse the repository at this point in the history
fixes Swishjam Slack bot sometimes not being part of channel, adds `cancellation_feedback_provided` stripe event
  • Loading branch information
CollinSchneider committed May 13, 2024
2 parents 7fee72e + 540014e commit 566c849
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 10 deletions.
29 changes: 28 additions & 1 deletion backend/app/lib/slack/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def list_channels(cursor: nil, exclude_archived: true, limit: 100, types: 'publi
end.flatten
end

def post_message_to_channel(channel:, text: nil, blocks: nil, thread_ts: nil, metadata_event_type: nil, metadata_event_payload: {}, unfurl_links: true, unfurl_media: true, __bypass_dev_flag: false)
def post_message_to_channel(channel:, text: nil, blocks: nil, thread_ts: nil, metadata_event_type: nil, metadata_event_payload: {}, unfurl_links: true, unfurl_media: true, __bypass_dev_flag: false, auto_join_channel_on_failure: true)
raise BadRequestError, "`post_message_to_channel` must contain either `text` or `blocks` argument." if text.blank? && blocks.blank?
if !Rails.env.production? && ENV['ENABLE_SLACK_NOTIFICATIONS_IN_DEV'] != 'true' && !__bypass_dev_flag
Rails.logger.info("\nWould have sent Slack message to channel #{channel} with text: #{text} and blocks: #{blocks}\n")
Expand All @@ -40,6 +40,24 @@ def post_message_to_channel(channel:, text: nil, blocks: nil, thread_ts: nil, me
payload[:thread_ts] = thread_ts if thread_ts.present?
post('chat.postMessage', payload)
end
rescue BadRequestError => e
if auto_join_channel_on_failure
join_channel(channel)
post_message_to_channel(
channel: channel,
text: text,
blocks: blocks,
thread_ts: thread_ts,
metadata_event_type: metadata_event_type,
metadata_event_payload: metadata_event_payload || {},
unfurl_links: unfurl_links,
unfurl_media: unfurl_media,
__bypass_dev_flag: __bypass_dev_flag,
auto_join_channel_on_failure: false
)
else
raise e
end
end

def list_teams
Expand Down Expand Up @@ -79,6 +97,15 @@ def add_reaction_to_message(channel:, message_ts:, emoji:)
post('reactions.add', { channel: channel, timestamp: message_ts, name: emoji })
end

def join_conversation(channel_id)
post('conversations.join', { channel: channel_id })
end
alias join_channel join_conversation

def list_permissions
get('apps.permissions.info')
end

private

def get(endpoint, params = {})
Expand Down
2 changes: 2 additions & 0 deletions backend/app/lib/stripe_helpers/supplemental_events/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def stripe_customer
stripe_record
elsif stripe_record.respond_to?(:customer) && stripe_record.customer.is_a?(Stripe::Customer)
stripe_record.customer
elsif @stripe_customer.is_a?(String)
@stripe_customer = Stripe::Customer.retrieve(@stripe_customer, stripe_account: @stripe_event.account)
else
@stripe_customer
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module StripeHelpers
module SupplementalEvents
class CancellationFeedbackReceived < Base
# record = Stripe::Subscription

def properties
mrr = nil
begin
mrr = StripeHelpers::MrrCalculator.calculate_for_stripe_subscription(stripe_record, include_canceled: true)
rescue => e
Sentry.capture_message("Failed to calculate MRR for subscription #{stripe_record.id} (#{e.message})")
end
{
stripe_subscription_id: stripe_record.id,
mrr: mrr,
cancellation_comment: stripe_record.cancellation_details&.comment,
cancellation_feedback: stripe_record.cancellation_details&.feedback,
cancellation_reason: stripe_record.cancellation_details&.reason,
}
end
end
end
end
16 changes: 14 additions & 2 deletions backend/app/lib/stripe_helpers/supplemental_events/evaluator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(stripe_event:, stripe_customer:, public_key:, maybe_user_profile_
def parsed_events_for_any_matching_supplemental_events
events = []
events << formatted_supplemental_event(StripeHelpers::SupplementalEvents::SubscriptionChurned) if is_churned_subscription_event?
events << formatted_supplemental_event(StripeHelpers::SupplementalEvents::CancellationFeedbackReceived) if just_received_cancellation_feedback?
events << formatted_supplemental_event(StripeHelpers::SupplementalEvents::NewFreeTrial) if is_new_free_trial_event?
events << formatted_supplemental_event(StripeHelpers::SupplementalEvents::NewActiveSubscription) if is_new_paid_subscription_event?
events << formatted_supplemental_event(StripeHelpers::SupplementalEvents::ChargeSucceeded) if @stripe_event.type == 'charge.succeeded'
Expand All @@ -36,7 +37,9 @@ def formatted_supplemental_event(event_class)
def is_new_paid_subscription_event?
return false unless ['customer.subscription.created', 'customer.subscription.updated'].include?(@stripe_event.type)
is_paid_subscription = stripe_object.items.data.any?{ |item| item.price.unit_amount.positive? }
is_paid_subscription && (stripe_object.status == 'active' || attribute_changed_to?('status', 'active'))
updated_to_active = @stripe_event.type == 'customer.subscription.updated' && attribute_changed_to?('status', 'active')
created_as_active = @stripe_event.type == 'customer.subscription.created' && stripe_object.status == 'active'
is_paid_subscription && (created_as_active || updated_to_active)
end

def is_churned_subscription_event?
Expand All @@ -56,14 +59,23 @@ def is_churned_subscription_event?
true
end

def just_received_cancellation_feedback?
@stripe_event.type == 'customer.subscription.updated' && attribute_changed?('cancellation_details')
end

def is_new_free_trial_event?
is_new_subscription_with_free_trial = @stripe_event.type == 'customer.subscription.created' && stripe_object.status == 'trialing'
subscription_updated_to_free_trial = @stripe_event.type == 'customer.subscription.updated' && attribute_changed_to?('status', 'trialing')
is_new_subscription_with_free_trial || subscription_updated_to_free_trial
end

def attribute_changed_to?(attribute_name, value)
previous_attributes.keys.include?(attribute_name.to_sym) && previous_attributes[attribute_name.to_s] != value && stripe_object[attribute_name.to_s] == value
attribute_changed?(attribute_name) && previous_attributes[attribute_name.to_s] != value && stripe_object[attribute_name.to_s] == value
end

def attribute_changed?(attribute_name)
# pretty sure it's always symbols, but just incase
previous_attributes.keys.include?(attribute_name.to_sym) || previous_attributes.keys.include?(attribute_name.to_s)
end

def stripe_object
Expand Down
2 changes: 1 addition & 1 deletion backend/config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Rails.application.configure do
# add ngrok to allowed hosts when locally developing!
config.hosts << '6c05-2603-8000-7200-9d38-e597-433-e53f-8fb4.ngrok-free.app'
config.hosts << 'b75f-2603-8000-7200-9d38-ac7c-5d8a-742c-7b64.ngrok-free.app'
# Settings specified here will take precedence over those in config/application.rb.

# In the development environment your application's code is reloaded any time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const SiteSelector = ({ sites, selectedSite, onSelect }) => {
const ConnectGoogleSearchConsoleView = () => {
const { token } = useAuthData();
const clientId = '411519113339-t2fidfed57o2pbkd2mc203in85k87fms.apps.googleusercontent.com';
const redirectHost = '56b9-2603-8000-7200-9d38-51d0-1b32-6c01-f3d5.ngrok-free.app';
const redirectHost = process.env.NEXT_PUBLIC_GOOGLE_OAUTH_REDIRECT_HOST || 'b75f-2603-8000-7200-9d38-ac7c-5d8a-742c-7b64.ngrok-free.app';
const redirectUri = `https://${redirectHost}/oauth/google/callback`;
const loginHint = 'collin@swishjam.com'
const scope = 'https://www.googleapis.com/auth/webmasters.readonly'
Expand Down
3 changes: 1 addition & 2 deletions dashboard/src/components/Integrations/ConnectViews/Slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ export default function SlackConnectionView() {
const redirectHost = process.env.NEXT_PUBLIC_SLACK_REDIRECT_HOST || 'capture.swishjam.com';
const redirectUrl = `https://${redirectHost}/oauth/slack/callback`;
const clientId = process.env.NEXT_PUBLIC_SLACK_CLIENT_ID || '3567839339057.6156356819525';
const scopes = ['channels:read', 'chat:write', 'commands', 'groups:read', 'im:read', 'im:write', 'incoming-webhook', 'mpim:read', 'reactions:write', 'users.profile:read', 'users:read', 'users:read.email'];
const scopes = ['channels:read', 'channels:join', 'chat:write', 'commands', 'groups:read', 'groups:write', 'im:read', 'im:write', 'mpim:read', 'reactions:write', 'users.profile:read', 'users:read', 'users:read.email'];
const oauthLink = `https://slack.com/oauth/v2/authorize?scope=${scopes.join(',')}&redirect_uri=${redirectUrl}&client_id=${clientId}&state=${authToken}`;

return (
<a
className='w-full mt-6 flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white focus:outline-none focus:ring-2 bg-swishjam hover:bg-swishjam-dark'
Expand Down
6 changes: 3 additions & 3 deletions dashboard/src/components/Reports/AddEditReport.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState, useRef } from 'react';
import { useEffect, useState } from 'react';
import SwishjamAPI from '@/lib/api-client/swishjam-api';
import { LuPlus, LuTrash } from "react-icons/lu";

Expand Down Expand Up @@ -228,7 +228,7 @@ export default function AddEditReport({
)}
/>

<FormField
{/* <FormField
control={form.control}
name="sending_mechanism"
render={({ field }) => (
Expand All @@ -250,7 +250,7 @@ export default function AddEditReport({
<FormMessage />
</FormItem>
)}
/>
/> */}

<FormField
control={form.control}
Expand Down

0 comments on commit 566c849

Please sign in to comment.