diff --git a/lib/muffin_man.rb b/lib/muffin_man.rb index 38b2d84..d45b588 100644 --- a/lib/muffin_man.rb +++ b/lib/muffin_man.rb @@ -40,6 +40,7 @@ require "muffin_man/uploads/v20201101" require "muffin_man/aplus_content/v20201101" require "muffin_man/application_management/v20231130" +require "muffin_man/replenishment/v20221107" module MuffinMan class Error < StandardError; end diff --git a/lib/muffin_man/replenishment/v20221107.rb b/lib/muffin_man/replenishment/v20221107.rb new file mode 100644 index 0000000..ad6568c --- /dev/null +++ b/lib/muffin_man/replenishment/v20221107.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module MuffinMan + module Replenishment + class V20221107 < SpApiClient + # Parameters for listOffers operation + LIST_OFFERS_PARAMS = %w[ + filters + sort + pageSize + nextToken + ].freeze + + # Parameters for listOfferMetrics operation + LIST_OFFER_METRICS_PARAMS = %w[ + filters + sort + pageSize + nextToken + ].freeze + + # Parameters for getSellingPartnerMetrics operation + GET_SELLING_PARTNER_METRICS_PARAMS = %w[ + filters + sort + pageSize + nextToken + ].freeze + + # Get all of a selling partner's replenishment offers filtered by specific criteria + # @param marketplace_ids [Array] A list of marketplace identifiers + # @param params [Hash] Optional filtering and pagination parameters + # @return [Typhoeus::Response] API response + def list_offers(marketplace_ids, params = {}) + @local_var_path = "/replenishment/2022-11-07/offers/search" + @request_body = build_search_request_body(marketplace_ids, params, LIST_OFFERS_PARAMS) + @request_type = "POST" + call_api + end + + # Get replenishment business metrics for each of a selling partner's offers + # @param marketplace_ids [Array] A list of marketplace identifiers + # @param params [Hash] Optional filtering and pagination parameters + # @return [Typhoeus::Response] API response + def list_offer_metrics(marketplace_ids, params = {}) + @local_var_path = "/replenishment/2022-11-07/offers/metrics/search" + @request_body = build_search_request_body(marketplace_ids, params, LIST_OFFER_METRICS_PARAMS) + @request_type = "POST" + call_api + end + + # Get a selling partner's replenishment business metrics + # @param marketplace_ids [Array] A list of marketplace identifiers + # @param params [Hash] Optional filtering and pagination parameters + # @return [Typhoeus::Response] API response + def get_selling_partner_metrics(marketplace_ids, params = {}) + @local_var_path = "/replenishment/2022-11-07/sellingPartners/metrics/search" + @request_body = build_search_request_body(marketplace_ids, params, GET_SELLING_PARTNER_METRICS_PARAMS) + @request_type = "POST" + call_api + end + + private + + # Build request body for search operations + # @param marketplace_ids [Array] A list of marketplace identifiers + # @param params [Hash] Parameters containing filters and other options + # @param allowed_params [Array] List of allowed parameters for this operation + # @return [Hash] Request body + def build_search_request_body(marketplace_ids, params, allowed_params) + marketplace_ids = Array(marketplace_ids) + + request_body = { + "marketplaceIds" => marketplace_ids + } + + # Add allowed parameters from params + allowed_params.each do |param| + request_body[param] = params[param] if params.key?(param) + end + + request_body + end + end + end +end diff --git a/spec/muffin_man/replenishment/v20221107_spec.rb b/spec/muffin_man/replenishment/v20221107_spec.rb new file mode 100644 index 0000000..d5571f7 --- /dev/null +++ b/spec/muffin_man/replenishment/v20221107_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +RSpec.describe MuffinMan::Replenishment::V20221107 do + subject(:replenishment_client) { described_class.new(credentials) } + + let(:marketplace_ids) { ["ATVPDKIKX0DER"] } + + before do + stub_request_access_token + end + + describe "#list_offers" do + it "makes a successful request with marketplace IDs only" do + stub_replenishment_list_offers + response = replenishment_client.list_offers(marketplace_ids) + expect(response.response_code).to eq(200) + expect(JSON.parse(response.body)["offers"]).to be_an(Array) + end + + it "makes a successful request with filters" do + params = { + "filters" => { + "enabledOnly" => true, + "asins" => ["B001234567"] + } + } + stub_replenishment_list_offers_with_filters + response = replenishment_client.list_offers(marketplace_ids, params) + expect(response.response_code).to eq(200) + end + + it "accepts single marketplace ID" do + stub_replenishment_list_offers + response = replenishment_client.list_offers("ATVPDKIKX0DER") + expect(response.response_code).to eq(200) + end + end + + describe "#list_offer_metrics" do + it "makes a successful request for offer metrics" do + params = { + "filters" => { + "timeInterval" => { + "startDate" => "2023-01-01T00:00:00Z", + "endDate" => "2023-01-31T23:59:59Z" + }, + "aggregationFrequency" => "MONTHLY" + } + } + stub_replenishment_list_offer_metrics + response = replenishment_client.list_offer_metrics(marketplace_ids, params) + expect(response.response_code).to eq(200) + expect(JSON.parse(response.body)["offerMetrics"]).to be_an(Array) + end + + it "makes a successful request with forecast metrics" do + params = { + "filters" => { + "timeInterval" => { + "startDate" => "2023-02-01T00:00:00Z", + "endDate" => "2023-04-30T23:59:59Z" + }, + "timePeriodType" => "FORECAST", + "aggregationFrequency" => "MONTHLY" + } + } + stub_replenishment_list_offer_metrics_forecast + response = replenishment_client.list_offer_metrics(marketplace_ids, params) + expect(response.response_code).to eq(200) + end + end + + describe "#get_selling_partner_metrics" do + it "makes a successful request for all metrics" do + params = { + "filters" => { + "timeInterval" => { + "startDate" => "2023-01-01T00:00:00Z", + "endDate" => "2023-01-31T23:59:59Z" + }, + "aggregationFrequency" => "MONTHLY" + } + } + stub_replenishment_get_selling_partner_metrics + response = replenishment_client.get_selling_partner_metrics(marketplace_ids, params) + expect(response.response_code).to eq(200) + expect(JSON.parse(response.body)["aggregatedMetrics"]).to be_an(Array) + end + + it "makes a successful request with specific metrics" do + params = { + "filters" => { + "timeInterval" => { + "startDate" => "2023-01-01T00:00:00Z", + "endDate" => "2023-01-31T23:59:59Z" + }, + "aggregationFrequency" => "MONTHLY", + "metrics" => ["SHIPPED_SUBSCRIPTION_UNITS", "TOTAL_SUBSCRIPTIONS_REVENUE"] + } + } + stub_replenishment_get_selling_partner_metrics_specific + response = replenishment_client.get_selling_partner_metrics(marketplace_ids, params) + expect(response.response_code).to eq(200) + end + + it "makes a successful request with forecast metrics" do + params = { + "filters" => { + "timeInterval" => { + "startDate" => "2023-02-01T00:00:00Z", + "endDate" => "2023-04-30T23:59:59Z" + }, + "timePeriodType" => "FORECAST", + "aggregationFrequency" => "MONTHLY" + } + } + stub_replenishment_get_selling_partner_metrics_forecast + response = replenishment_client.get_selling_partner_metrics(marketplace_ids, params) + expect(response.response_code).to eq(200) + end + end +end diff --git a/spec/support/replenishment/get_selling_partner_metrics.json b/spec/support/replenishment/get_selling_partner_metrics.json new file mode 100644 index 0000000..b65462c --- /dev/null +++ b/spec/support/replenishment/get_selling_partner_metrics.json @@ -0,0 +1,62 @@ +{ + "aggregatedMetrics": [ + { + "timeInterval": { + "startDate": "2023-01-01T00:00:00Z", + "endDate": "2023-01-31T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "marketplaceId": "ATVPDKIKX0DER", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "value": 1250 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "value": 15750.00, + "currencyCode": "USD" + }, + { + "metric": "ACTIVE_SUBSCRIPTIONS", + "value": 485 + }, + { + "metric": "NOT_DELIVERED_DUE_TO_OOS", + "value": 15 + }, + { + "metric": "SUBSCRIBER_NON_SUBSCRIBER_AVERAGE_REVENUE", + "value": 32.47, + "currencyCode": "USD" + }, + { + "metric": "LOST_REVENUE_DUE_TO_OOS", + "value": 180.00, + "currencyCode": "USD" + }, + { + "metric": "COUPONS_REVENUE_PENETRATION", + "value": 12.5 + }, + { + "metric": "REVENUE_BY_DELIVERIES", + "value": 12.6, + "currencyCode": "USD" + }, + { + "metric": "SUBSCRIBER_RETENTION", + "value": 87.2 + }, + { + "metric": "REVENUE_PENETRATION_BY_SELLER_FUNDING", + "value": 65.3 + }, + { + "metric": "SHARE_OF_COUPON_SUBSCRIPTIONS", + "value": 8.9 + } + ] + } + ] +} diff --git a/spec/support/replenishment/get_selling_partner_metrics_forecast.json b/spec/support/replenishment/get_selling_partner_metrics_forecast.json new file mode 100644 index 0000000..30226f7 --- /dev/null +++ b/spec/support/replenishment/get_selling_partner_metrics_forecast.json @@ -0,0 +1,48 @@ +{ + "aggregatedMetrics": [ + { + "timeInterval": { + "startDate": "2023-02-01T00:00:00Z", + "endDate": "2023-04-30T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "timePeriodType": "FORECAST", + "marketplaceId": "ATVPDKIKX0DER", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "30_DAYS", + "value": 425 + }, + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "60_DAYS", + "value": 850 + }, + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "90_DAYS", + "value": 1275 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "30_DAYS", + "value": 5525.00, + "currencyCode": "USD" + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "60_DAYS", + "value": 11050.00, + "currencyCode": "USD" + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "90_DAYS", + "value": 16575.00, + "currencyCode": "USD" + } + ] + } + ] +} diff --git a/spec/support/replenishment/get_selling_partner_metrics_specific.json b/spec/support/replenishment/get_selling_partner_metrics_specific.json new file mode 100644 index 0000000..e9e77f6 --- /dev/null +++ b/spec/support/replenishment/get_selling_partner_metrics_specific.json @@ -0,0 +1,23 @@ +{ + "aggregatedMetrics": [ + { + "timeInterval": { + "startDate": "2023-01-01T00:00:00Z", + "endDate": "2023-01-31T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "marketplaceId": "ATVPDKIKX0DER", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "value": 1250 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "value": 15750.00, + "currencyCode": "USD" + } + ] + } + ] +} diff --git a/spec/support/replenishment/list_offer_metrics.json b/spec/support/replenishment/list_offer_metrics.json new file mode 100644 index 0000000..2e365ee --- /dev/null +++ b/spec/support/replenishment/list_offer_metrics.json @@ -0,0 +1,59 @@ +{ + "offerMetrics": [ + { + "asin": "B001234567", + "sellerSku": "SKU-TEST-001", + "marketplaceId": "ATVPDKIKX0DER", + "timeInterval": { + "startDate": "2023-01-01T00:00:00Z", + "endDate": "2023-01-31T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "value": 125 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "value": 1250.00, + "currencyCode": "USD" + }, + { + "metric": "ACTIVE_SUBSCRIPTIONS", + "value": 45 + }, + { + "metric": "SUBSCRIBER_RETENTION", + "value": 85.5 + } + ] + }, + { + "asin": "B007654321", + "sellerSku": "SKU-TEST-002", + "marketplaceId": "ATVPDKIKX0DER", + "timeInterval": { + "startDate": "2023-01-01T00:00:00Z", + "endDate": "2023-01-31T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "value": 80 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "value": 960.00, + "currencyCode": "USD" + }, + { + "metric": "ACTIVE_SUBSCRIPTIONS", + "value": 25 + } + ] + } + ], + "nextToken": "eyJuZXh0VG9rZW4iOiIxMjM0NTY3ODkwIn0=" +} diff --git a/spec/support/replenishment/list_offer_metrics_forecast.json b/spec/support/replenishment/list_offer_metrics_forecast.json new file mode 100644 index 0000000..359714c --- /dev/null +++ b/spec/support/replenishment/list_offer_metrics_forecast.json @@ -0,0 +1,50 @@ +{ + "offerMetrics": [ + { + "asin": "B001234567", + "sellerSku": "SKU-TEST-001", + "marketplaceId": "ATVPDKIKX0DER", + "timeInterval": { + "startDate": "2023-02-01T00:00:00Z", + "endDate": "2023-04-30T23:59:59Z" + }, + "aggregationFrequency": "MONTHLY", + "timePeriodType": "FORECAST", + "metrics": [ + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "30_DAYS", + "value": 35 + }, + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "60_DAYS", + "value": 70 + }, + { + "metric": "SHIPPED_SUBSCRIPTION_UNITS", + "forecastPeriod": "90_DAYS", + "value": 105 + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "30_DAYS", + "value": 420.00, + "currencyCode": "USD" + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "60_DAYS", + "value": 840.00, + "currencyCode": "USD" + }, + { + "metric": "TOTAL_SUBSCRIPTIONS_REVENUE", + "forecastPeriod": "90_DAYS", + "value": 1260.00, + "currencyCode": "USD" + } + ] + } + ] +} diff --git a/spec/support/replenishment/list_offers.json b/spec/support/replenishment/list_offers.json new file mode 100644 index 0000000..d04c725 --- /dev/null +++ b/spec/support/replenishment/list_offers.json @@ -0,0 +1,48 @@ +{ + "offers": [ + { + "asin": "B001234567", + "sellerSku": "SKU-TEST-001", + "marketplaceId": "ATVPDKIKX0DER", + "offerState": "ENABLED", + "subscriptionDiscount": { + "percentage": 15.0, + "isSellerFunded": true + }, + "autoEnrollment": { + "enabled": true, + "eligibilityType": "NEW_CUSTOMER" + }, + "deliveryFrequencies": [ + { + "unit": "MONTH", + "value": 1 + }, + { + "unit": "MONTH", + "value": 2 + } + ] + }, + { + "asin": "B007654321", + "sellerSku": "SKU-TEST-002", + "marketplaceId": "ATVPDKIKX0DER", + "offerState": "DISABLED", + "subscriptionDiscount": { + "percentage": 10.0, + "isSellerFunded": false + }, + "autoEnrollment": { + "enabled": false + }, + "deliveryFrequencies": [ + { + "unit": "MONTH", + "value": 1 + } + ] + } + ], + "nextToken": "eyJuZXh0VG9rZW4iOiIxMjM0NTY3ODkwIn0=" +} diff --git a/spec/support/replenishment/list_offers_filtered.json b/spec/support/replenishment/list_offers_filtered.json new file mode 100644 index 0000000..3294087 --- /dev/null +++ b/spec/support/replenishment/list_offers_filtered.json @@ -0,0 +1,24 @@ +{ + "offers": [ + { + "asin": "B001234567", + "sellerSku": "SKU-TEST-001", + "marketplaceId": "ATVPDKIKX0DER", + "offerState": "ENABLED", + "subscriptionDiscount": { + "percentage": 15.0, + "isSellerFunded": true + }, + "autoEnrollment": { + "enabled": true, + "eligibilityType": "NEW_CUSTOMER" + }, + "deliveryFrequencies": [ + { + "unit": "MONTH", + "value": 1 + } + ] + } + ] +} diff --git a/spec/support/sp_api_helpers.rb b/spec/support/sp_api_helpers.rb index 3371f8e..c7d9e43 100644 --- a/spec/support/sp_api_helpers.rb +++ b/spec/support/sp_api_helpers.rb @@ -519,6 +519,41 @@ def stub_vendor_transaction_status_v1_get_transaction .to_return(status: 200, body: File.read("./spec/support/vendor_transaction_status/v1_get_transaction.json"), headers: {}) end + def stub_replenishment_list_offers + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/offers/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/list_offers.json"), headers: {}) + end + + def stub_replenishment_list_offers_with_filters + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/offers/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/list_offers_filtered.json"), headers: {}) + end + + def stub_replenishment_list_offer_metrics + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/offers/metrics/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/list_offer_metrics.json"), headers: {}) + end + + def stub_replenishment_list_offer_metrics_forecast + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/offers/metrics/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/list_offer_metrics_forecast.json"), headers: {}) + end + + def stub_replenishment_get_selling_partner_metrics + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/sellingPartners/metrics/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/get_selling_partner_metrics.json"), headers: {}) + end + + def stub_replenishment_get_selling_partner_metrics_specific + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/sellingPartners/metrics/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/get_selling_partner_metrics_specific.json"), headers: {}) + end + + def stub_replenishment_get_selling_partner_metrics_forecast + stub_request(:post, "https://#{hostname}/replenishment/2022-11-07/sellingPartners/metrics/search") + .to_return(status: 200, body: File.read("./spec/support/replenishment/get_selling_partner_metrics_forecast.json"), headers: {}) + end + def credentials { refresh_token: "a-refresh-token",