# Solidus Webhooks [](https://circleci.com/gh/solidusio-contrib/solidus_webhooks) [](https://codecov.io/gh/solidusio-contrib/solidus_webhooks) ## Installation Add solidus_webhooks to your Gemfile: ```ruby gem 'solidus_webhooks' ``` Bundle your dependencies and run the installation generator: ```shell bin/rails generate solidus_webhooks:install ``` ## Usage A Webhook receiver is just a callable and can be registered in the Solidus configuration as follows: ```ruby SolidusWebhooks.config.register_webhook_handler :tracking_number, -> payload { order = Spree::Order.find_by!(number: payload[:order]) shipment = order.shipments.find_by!(number: payload[:shipment]) shipment.update!(tracking: payload[:tracking]) } ``` This will enable sending `POST` requests to `/webhooks/tracking-number` with a JSON payload like this: ```json { "order": "R1234567890", "shipment": "S1234567890", "tracking": "T123-456-789" } ``` ### Handlers requirements The only requirement on handlers is for them to respond to `#call` and accept a payload. Example: ```ruby module TrackingNumberHandler def self.call(payload) order = Spree::Order.find_by!(number: payload[:order]) shipment = order.shipments.find_by!(number: payload[:shipment]) shipment.update!(tracking: payload[:tracking]) end end SolidusWebhooks.config.register_webhook_handler :tracking_number, TrackingNumberHandler ``` ### Making the handler asynchronous To make a handler asynchronous just make its implementation internally call your preferred job handler (e.g. ActiveJob). In most cases you'll want to filter, prepare, and validate the payload for the job of your choice, to avoid ingesting and invalid input. Example: ```ruby SolidusWebhooks.config.register_webhook_handler :tracking_number, -> payload { UpdateTrackingNumberJob.perform_later( order: payload.fetch(:order) shipment: payload.fetch(:shipment) tracking: payload.fetch(:tracking) ) } ``` ### Payload routing If your handler can receive different kind of payloads the most common technique is to route them to appropriate sub-handlers (that can be an ActiveJob class or a service class). ```ruby SolidusWebhooks.config.register_webhook_handler :tracking_number, -> payload { case payload[:tracking] when /^FOO(\d+-)+/ UpdateFooTrackingNumberJob.perform_later( order: payload.fetch(:order) shipment: payload.fetch(:shipment) tracking: payload.fetch(:tracking) ) when /^BAR(\d+-)+/ UpdateBarTrackingNumberJob.perform_later( order: payload.fetch(:order) shipment: payload.fetch(:shipment) tracking: payload.fetch(:tracking) ) else raise "unknown tracking service" end } ``` ### Restricting Permissions It's good practice not to use admin-user tokens for webhooks, instead you should define a permission set tied to the webhook handler you're providing. Use the standard Solidus permission-sets to do that. Example: ```ruby module ReceiveTrackingWebhookPermission < Spree::PermissionSets::Base def activate! can :receive, Spree::Webhook do |webhook| webhook.id == :tracking_number end end end Spree::RoleConfiguration.configure do |config| config.assign_permissions :foo_tracking_service, %w[ ReceiveTrackingWebhookPermission ] end ``` ### Accessing the API User If you need to access the API user that is making the call to the webhook, you can just accept an additional argument in the callable handler. Example: ```ruby SolidusWebhooks.config.register_webhook_handler :tracking_number, -> payload, user { Rails.logger.info "Received payload from user #{user.email}: #{payload.to_json}" # … } ``` ## Development ### Testing the extension First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy app if it does not exist, then it will run specs. The dummy app can be regenerated by using `bin/rake extension:test_app`. ```shell bin/rake ``` To run [Rubocop](https://github.com/bbatsov/rubocop) static code analysis run ```shell bundle exec rubocop ``` When testing your application's integration with this extension you may use its factories. Simply add this require statement to your spec_helper: ```ruby require 'solidus_webhooks/factories' ``` ### Running the sandbox To run this extension in a sandboxed Solidus application, you can run `bin/sandbox`. The path for the sandbox app is `./sandbox` and `bin/rails` will forward any Rails commands to `sandbox/bin/rails`. Here's an example: ``` $ bin/rails server => Booting Puma => Rails 6.0.2.1 application starting in development * Listening on tcp://127.0.0.1:3000 Use Ctrl-C to stop ``` ### Updating the changelog Before and after releases the changelog should be updated to reflect the up-to-date status of the project: ```shell bin/rake changelog git add CHANGELOG.md git commit -m "Update the changelog" ``` ### Releasing new versions Your new extension version can be released using `gem-release` like this: ```shell bundle exec gem bump -v 1.6.0 bin/rake changelog git add CHANGELOG.md git commit --amend --no-edit gem tag git push --tags bundle exec gem release ``` ## License Copyright (c) 2020 Nebulab srls, released under the New BSD License.