-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
689 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: Tests | ||
|
||
on: push | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Ruby | ||
uses: ruby/setup-ruby@v1 | ||
with: | ||
bundler-cache: true | ||
|
||
- name: Run tests | ||
run: | | ||
bundle exec ruby test/*_test.rb |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.3.5 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
source "https://rubygems.org" | ||
|
||
ruby file: ".ruby-version" | ||
|
||
gem "sinatra", github: "sinatra", require: "sinatra/base" | ||
gem "rackup" | ||
gem "puma" | ||
|
||
gem "activesupport", require: "active_support/all" | ||
gem "logger" | ||
gem "json" | ||
|
||
group :test do | ||
gem "minitest" | ||
gem "rack-test" | ||
end | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
GIT | ||
remote: https://github.com/sinatra/sinatra.git | ||
revision: 973c936319af9132d7ab2f60985e359d0c75c93e | ||
specs: | ||
rack-protection (4.0.0) | ||
base64 (>= 0.1.0) | ||
logger (>= 1.6.0) | ||
rack (>= 3.0.0, < 4) | ||
sinatra (4.0.0) | ||
logger (>= 1.6.0) | ||
mustermann (~> 3.0) | ||
rack (>= 3.0.0, < 4) | ||
rack-protection (= 4.0.0) | ||
rack-session (>= 2.0.0, < 3) | ||
tilt (~> 2.0) | ||
|
||
GEM | ||
remote: https://rubygems.org/ | ||
specs: | ||
activesupport (7.2.1) | ||
base64 | ||
bigdecimal | ||
concurrent-ruby (~> 1.0, >= 1.3.1) | ||
connection_pool (>= 2.2.5) | ||
drb | ||
i18n (>= 1.6, < 2) | ||
logger (>= 1.4.2) | ||
minitest (>= 5.1) | ||
securerandom (>= 0.3) | ||
tzinfo (~> 2.0, >= 2.0.5) | ||
base64 (0.2.0) | ||
bigdecimal (3.1.8) | ||
concurrent-ruby (1.3.4) | ||
connection_pool (2.4.1) | ||
drb (2.2.1) | ||
i18n (1.14.5) | ||
concurrent-ruby (~> 1.0) | ||
json (2.7.2) | ||
logger (1.6.1) | ||
minitest (5.25.1) | ||
mustermann (3.0.3) | ||
ruby2_keywords (~> 0.0.1) | ||
nio4r (2.7.3) | ||
puma (6.4.2) | ||
nio4r (~> 2.0) | ||
rack (3.1.7) | ||
rack-session (2.0.0) | ||
rack (>= 3.0.0) | ||
rack-test (2.1.0) | ||
rack (>= 1.3) | ||
rackup (2.1.0) | ||
rack (>= 3) | ||
webrick (~> 1.8) | ||
ruby2_keywords (0.0.5) | ||
securerandom (0.3.1) | ||
tilt (2.4.0) | ||
tzinfo (2.0.6) | ||
concurrent-ruby (~> 1.0) | ||
webrick (1.8.1) | ||
|
||
PLATFORMS | ||
arm64-darwin-23 | ||
ruby | ||
|
||
DEPENDENCIES | ||
activesupport | ||
json | ||
logger | ||
minitest | ||
puma | ||
rack-test | ||
rackup | ||
sinatra! | ||
|
||
RUBY VERSION | ||
ruby 3.3.5p100 | ||
|
||
BUNDLED WITH | ||
2.5.18 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: bundle exec puma -p $PORT |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,22 @@ | ||
# locali-ge.csa-admin | ||
Small Sinatra app to handle locali-ge.ch woocommerce webhooks | ||
# locali-ge.csa-admin.org | ||
|
||
A small Sinatra app to handle locali-ge.ch WooCommerce webhooks and automatically create a new member in the CSA Admin organization. | ||
|
||
The mapping of the WooCommerce products to the CSA Admin organization resources is handled in the `config/mapping.yml` file. The `api_endpoint` of the CSA Admin API must be set in the `config/config.yml` file for each organization. | ||
|
||
## Deployment | ||
|
||
The `WEBHOOK_SECRET` environment variable must be set to the WooCommerce webhook secret. | ||
|
||
For each organization, the `<ORGANIZATION>_API_TOKEN` environment variable must be set. | ||
|
||
## Testing | ||
|
||
```sh | ||
bundle install | ||
bundle exec ruby test/*_test.rb | ||
``` | ||
|
||
## Author | ||
|
||
[Thibaud Guillaume-Gentil](https://thibaud.gg) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
require 'bundler/setup' | ||
Bundler.require(:default) | ||
|
||
require_relative 'lib/webhook' | ||
|
||
class App < Sinatra::Base | ||
configure :production, :development do | ||
enable :logging | ||
end | ||
|
||
before do | ||
@request_body = request.body.read | ||
end | ||
|
||
post '/webhook' do | ||
verify_signature! | ||
payload = parse_payload | ||
|
||
logger.info payload | ||
begin | ||
member_params = Webhook.handle!(payload) | ||
logger.info member_params | ||
rescue ArgumentError => e | ||
logger.info e.message | ||
end | ||
|
||
status 204 | ||
end | ||
|
||
private | ||
|
||
def verify_signature! | ||
signature = request.env['HTTP_X_WC_WEBHOOK_SIGNATURE'] | ||
secret = ENV['WEBHOOK_SECRET'] | ||
|
||
computed_hmac = Base64.strict_encode64( | ||
OpenSSL::HMAC.digest('sha256', secret, @request_body)) | ||
|
||
unless signature && Rack::Utils.secure_compare(computed_hmac, signature) | ||
halt 403, 'Forbidden' | ||
end | ||
end | ||
|
||
def parse_payload | ||
JSON.parse(@request_body) | ||
rescue JSON::ParserError | ||
halt 400, 'Invalid JSON' | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
require './app' | ||
run App |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
cocagne: | ||
store_id: 31 | ||
api_endpoint: https://admin.cocagne.ch/api/v1/members | ||
basket_sizes: | ||
35087: 1 # Grande part | ||
basket_complements: | ||
34780: 9 # Oeufs 6 pièces | ||
34781: 10 # Pain Cora blé 500g | ||
34782: 12 # Pain Cora épeautre 450g | ||
34783: 6 # Fromage frais | ||
34787: 14 # Jus de pomme regarge | ||
depots: | ||
34088: 43 # 16 ch. Franconis 1290 Versoix | ||
34087: 22 # 15 ch. de Roches 1208 Genève |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
require 'yaml' | ||
|
||
class Webhook | ||
attr_reader :payload | ||
|
||
def self.handle!(payload) | ||
new(payload).handle! | ||
end | ||
|
||
def initialize(payload) | ||
@payload = payload | ||
end | ||
|
||
def handle! | ||
ensure_mapping! | ||
|
||
member_params | ||
end | ||
|
||
private | ||
|
||
def ensure_mapping! | ||
return if mapping | ||
|
||
store_name = @payload.dig("store", "name") | ||
raise ArgumentError, "No mapping found for store: #{store_id} (#{store_name})" | ||
end | ||
|
||
def member_params | ||
{ | ||
organization: organization, | ||
name: "#{billing["last_name"]} #{billing["first_name"]}", | ||
emails: billing["email"], | ||
phones: billing["phone"], | ||
address: [billing["address_1"], billing["address_2"]].map(&:presence).compact.join(', '), | ||
city: billing["city"], | ||
zip: billing["postcode"], | ||
country_code: billing["country"], | ||
waiting_basket_size_id: mapping_id_for("basket_sizes"), | ||
waiting_depot_id: mapping_id_for("depots"), | ||
members_basket_complements_attributes: basket_complements | ||
} | ||
end | ||
|
||
def mapping | ||
@mapping ||= YAML.load_file('./config/mapping.yml').detect { |name, v| | ||
v['store_id'] == store_id | ||
} | ||
end | ||
|
||
def organization | ||
mapping.first | ||
end | ||
|
||
def basket_complements | ||
mapping_ids_for("basket_complements").map { |id| | ||
{ basket_complement_id: id, quanity: 1 } | ||
} | ||
end | ||
|
||
def mapping_id_for(type) | ||
mapping.last[type].each { |product_id, id| | ||
return id if product_id.in?(product_ids) | ||
} | ||
nil | ||
end | ||
|
||
def mapping_ids_for(type) | ||
ids = [] | ||
mapping.last[type].each { |product_id, id| | ||
ids << id if product_id.in?(product_ids) | ||
} | ||
ids | ||
end | ||
|
||
def product_ids | ||
@ids ||= begin | ||
ids =[] | ||
@payload.fetch("line_items").each { |item| | ||
ids << item["product_id"] | ||
item["meta_data"].each { |meta| | ||
if meta["key"] == "selected_item_post_id" | ||
meta["value"].each { |v| v.values.each { |v| ids += Array(v["value"]) } } | ||
end | ||
} | ||
} | ||
ids.map(&:to_i) | ||
end | ||
end | ||
|
||
def billing | ||
@billing ||= @payload.fetch("billing") | ||
end | ||
|
||
def store_id | ||
@payload.dig("store", "id") | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
ENV['RACK_ENV'] = 'test' | ||
|
||
require 'bundler/setup' | ||
Bundler.require(:default, :test) | ||
|
||
require_relative '../app' | ||
require "minitest/autorun" | ||
|
||
class AppTest < Minitest::Test | ||
include Rack::Test::Methods | ||
|
||
def app; App end | ||
|
||
def setup | ||
@secret = 'test_secret' | ||
ENV['WEBHOOK_SECRET'] = @secret | ||
end | ||
|
||
def request(payload, secret: nil) | ||
secret ||= @secret | ||
signature = Base64.strict_encode64( | ||
OpenSSL::HMAC.digest('sha256', secret, payload)) | ||
|
||
header "Content-Type", 'application/json' | ||
header "X-WC-Webhook-Signature", signature | ||
|
||
post "/webhook", payload | ||
end | ||
|
||
def test_valid_webhook_request | ||
payload = File.read('test/fixtures/order_created.json') | ||
|
||
request(payload) | ||
|
||
assert_equal 204, last_response.status | ||
assert_empty last_response.body | ||
end | ||
|
||
def test_unknown_store | ||
payload = { "store" => { "id" => 999, "name" => "Unknown" } }.to_json | ||
|
||
request(payload) | ||
|
||
assert_equal 204, last_response.status | ||
assert_empty last_response.body | ||
end | ||
|
||
def test_invalid_signature | ||
payload = { "test_key" => "test_value" }.to_json | ||
|
||
request(payload, secret: "wrong_secret") | ||
|
||
assert_equal 403, last_response.status | ||
assert_equal "Forbidden", last_response.body | ||
end | ||
|
||
def test_missing_signature_header | ||
payload = { "test_key" => "test_value" }.to_json | ||
|
||
header "Content-Type", 'application/json' | ||
post "/webhook", payload | ||
|
||
assert_equal 403, last_response.status | ||
assert_equal "Forbidden", last_response.body | ||
end | ||
|
||
def test_invalid_json_payload | ||
payload = "invalid_json" | ||
|
||
request(payload) | ||
|
||
assert_equal 400, last_response.status | ||
assert_includes last_response.body, "Invalid JSON" | ||
end | ||
end |
Oops, something went wrong.