restarone / violet_rails

an app engine for your business. Seamlessly implement business logic with a powerful API. Out of the box CMS, blog, forum and email functionality. Developer friendly & easily extendable for your next SaaS/XaaS project. Built with Rails 6, Devise, Sidekiq & PostgreSQL
https://violet.restarone.solutions
MIT License
96 stars 43 forks source link

Violet Rails mailchimp plugin #720

Open donrestarone opened 2 years ago

donrestarone commented 2 years ago

credits to @Pralish

Mailchimp Sync V1 (soft deprecated) please use V2 below

class SyncToMailchimp
  def initialize(parameters)  
    @external_api_client = parameters[:external_api_client]
    @unsynced_api_resources = @external_api_client.api_namespace.api_resources.where("properties @> ?", {synced_to_mailchimp: false}.to_json)
        @mailchimp_uri = "https://#{@external_api_client.metadata['SERVER_PREFIX']}.api.mailchimp.com/3.0/lists/#{@external_api_client.metadata['LIST_ID']}/members?skip_merge_validation=true"
  end

  def start
    logger_namespace = ApiNamespace.find_by!(slug: @external_api_client.metadata["LOGGER_NAMESPACE"])
        @unsynced_api_resources.each do |api_resource|
       begin
                    attr_to_exclude = @external_api_client.metadata['ATTR_TO_EXCLUDE']
                  merge_fields = api_resource.properties.except(*attr_to_exclude).transform_keys(&:upcase).transform_values(&:to_s).merge({"IMPROVE": api_resource.properties["what_were_you_trying_to_overcome_or_improve"].to_s})
          response = HTTParty.post(@mailchimp_uri, 
            body: { 
              "email_address": api_resource.properties["email"],
              "status": "subscribed",
              "merge_fields": merge_fields,
              "tags": @external_api_client.metadata['TAGS']
              }.to_json,
            :headers => { 'Content-Type' => 'application/json', 'Authorization' => "Basic #{@external_api_client.metadata["API_KEY"]}" } )
         if response.success?
            api_resource.properties["synced_to_mailchimp"] = true
                        api_resource.save
         end
         logger_namespace.api_resources.create!(
                properties: {
                  api_resource: api_resource.id,
                        status: response.success? ? "success" : "error",
                  response: JSON.parse(response.body),
                  timestamp: Time.zone.now
                }
          )
       rescue StandardError => e
         logger_namespace.api_resources.create!(
              properties: {
                api_resource: api_resource.id,
                    status: "error",
                response: { detail: e.message},
                timestamp: Time.zone.now
              }
            )
       end
     end
  end
end
# at the end of the file we have to implicitly return the class 
SyncToMailchimp

The following metadata need to be included for the plugin to work:

Group 1172

API_KEY: mailchimp api key - https://mailchimp.com/help/about-api-keys/

LIST_ID: Audiance ID, - https://mailchimp.com/help/find-audience-id/

SERVER_PREFIX: mailchimp url prefix, eg: us9, us10 - https://mailchimp.com/developer/marketing/guides/quick-start/#make-your-first-api-call

ATTR_TO_EXCLUDE: attributes that you don't want to sync to mailchimp

TAGS: array of tags to assign to contact

LOGGER_NAMESPACE: the slug of the API Namespace you want to use for logging

Note:

Excluding synced_to_mailchimp attribute because it is for internal use
Excluded what_were_you_trying_to_overcome_or_improve intentionally and created a short tag name 'IMPROVE' because mailchimp didn't accept tag name that long.

merge fields

172187243-1952cfc9-c39f-4e14-8df3-4802531982d1

The field label can be anything but the tags should be exactly same as the keys in api namespace.

Note: The tags are capitalized by mailchimp

donrestarone commented 2 years ago

@Pralish when you get a chance, can you create a unit test for this plugin?

you can copy paste the class here: https://github.com/restarone/violet_rails/blob/master/test/fixtures/external_api_clients.yml

and use the web_mock gem to mock responses from Mailchimp

donrestarone commented 2 years ago

mailchimp sync v2

credits to @Pralish

class SyncToMailchimp
  def initialize(parameters)
    @external_api_client     = parameters[:external_api_client]
    @api_key                 = @external_api_client.metadata["API_KEY"]
    @unsynced_api_resources  = @external_api_client.api_namespace.api_resources.where("properties @> ?", {synced_to_mailchimp: false}.to_json)
    @mailchimp_uri           = "https://#{@external_api_client.metadata['SERVER_PREFIX']}.api.mailchimp.com/3.0/lists/#{@external_api_client.metadata['LIST_ID']}/members?skip_merge_validation=true"
    @custom_merge_fields_map = @external_api_client.metadata['CUSTOM_MERGE_FIELDS_MAP'] || {}
    @attr_to_exclude         = (@external_api_client.metadata['ATTR_TO_EXCLUDE'] || []) + @custom_merge_fields_map.keys + ['synced_to_mailchimp']
    @logger_namespace        = ApiNamespace.find_by(slug: @external_api_client.metadata["LOGGER_NAMESPACE"]) if @external_api_client.metadata["LOGGER_NAMESPACE"]
  end

  def start
    @unsynced_api_resources.each do |api_resource|
      begin
        merge_fields = api_resource.properties.except(*@attr_to_exclude).transform_keys(&:upcase).transform_values(&:to_s)

        @custom_merge_fields_map.each do |key, value|
          merge_fields[value.upcase] = api_resource.properties[key].to_s if value
        end

        response = HTTParty.post(@mailchimp_uri, 
          body: { 
              email_address: api_resource.properties["email"],
              status: "subscribed",
              merge_fields: merge_fields,
              tags: @external_api_client.metadata['TAGS'] || []
            }.to_json,

          headers: {
              'Content-Type': 'application/json',
              'Authorization': "Basic #{@api_key}" 
            } 
          )

        if response.success?
          api_resource.properties["synced_to_mailchimp"] = true
          api_resource.save
        end

        @logger_namespace.api_resources.create!(
          properties: {
            api_resource: api_resource.id,
            status: response.success? ? "success" : "error",
            response: JSON.parse(response.body),
            timestamp: Time.zone.now
          }
        ) if @logger_namespace

      rescue StandardError => e
        @logger_namespace.api_resources.create!(
          properties: {
            api_resource: api_resource.id,
            status: "error",
            response: { detail: e.message},
            timestamp: Time.zone.now
          }
        ) if @logger_namespace
      end
    end
  end
end

# at the end of the file we have to implicitly return the class 
SyncToMailchimp

Required api_namespace properties

email :

synced_to_mailchimp :

Required Metadata

API_KEY:

LIST_ID:

SERVER_PREFIX:

Optional Metadata

CUSTOM_MERGE_FIELDS_MAP

ATTR_TO_EXCLUDE:

TAGS:

LOGGER_NAMESPACE:

Screen Shot 2022-07-09 at 10 50 24 AM Screen Shot 2022-07-09 at 11 32 06 AM

On Mailchimp:

Create fields on mailchimp with same name as properties on api namespace. You can create fields with different names from api namespace properties, but you will need to map it correctly using CUSTOM_MERGE_FIELDS_MAP metadata.

https://user-images.githubusercontent.com/50227291/172191081-97393f39-9b15-46cb-a23f-f7bfa369ff6b.mov