Add VineVoucherRedeemerService

It handles redeeming a voucher
This commit is contained in:
Gaetan Craig-Riou
2024-10-21 21:52:13 +11:00
committed by Rachel Arnould
parent c17eddd69b
commit 9399c7e129
3 changed files with 346 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# frozen_string_literal: true
class VineVoucherRedeemerService
attr_reader :order, :errors
def initialize(order: )
@order = order
@errors = {}
end
def call
# Do nothing if we don't have a vine voucher added to the order
voucher_adjustment = order.voucher_adjustments.first
@voucher = voucher_adjustment&.originator
return true if voucher_adjustment.nil? || !@voucher.vine?
if vine_settings.nil?
errors[:vine_settings] = I18n.t("vine_voucher_redeemer_service.errors.vine_settings")
return false
end
response = call_vine_api
if !response.success?
handle_errors(response)
return false
end
voucher_adjustment.close
true
rescue Faraday::Error => e
Rails.logger.error e.inspect
Bugsnag.notify(e)
errors[:vine_api] = I18n.t("vine_voucher_validator_service.errors.vine_api")
false
end
private
def vine_settings
ConnectedApps::Vine.find_by(enterprise: order.distributor)&.data
end
def call_vine_api
jwt_service = VineJwtService.new(secret: vine_settings["secret"])
vine_api = VineApiService.new(api_key: vine_settings["api_key"], jwt_generator: jwt_service)
# Voucher amount is stored in dollars, VINE expect cents
vine_api.voucher_redemptions(
@voucher.external_voucher_id, @voucher.external_voucher_set_id, (@voucher.amount * 100)
)
end
def handle_errors(response)
if response.status == 400
errors[:redeeming_failed] = I18n.t("vine_voucher_redeemer_service.errors.redeeming_failed")
else
errors[:vine_api] = I18n.t("vine_voucher_redeemer_service.errors.vine_api")
end
end
end

View File

@@ -580,6 +580,11 @@ en:
vine_api: "There was an error communicating with the API"
invalid_voucher: "The voucher is not valid"
not_found_voucher: "The voucher doesn't exist"
vine_voucher_redeemer_service:
errors:
vine_settings: "No Vine api settings for the given enterprise"
vine_api: "There was an error communicating with the API"
redeeming_failed: "Redeeming the voucher failed"
actions:
create_and_add_another: "Create and Add Another"
create: "Create"

View File

@@ -0,0 +1,277 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe VineVoucherRedeemerService, feature: :connected_apps do
subject(:voucher_redeemer_service) { described_class.new(order: ) }
let(:user) { order.user }
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:order_cycle, distributors: [distributor]) }
let(:order) { create(:order_with_line_items, line_items_count: 1, distributor:, order_cycle:) }
let(:vine_voucher) {
create(:voucher_flat_rate, voucher_type: "VINE", code: 'some_code', enterprise: distributor,
amount: 6, external_voucher_id: voucher_id,
external_voucher_set_id: voucher_set_id )
}
let(:voucher_id) { "9d316d27-0dad-411a-8953-316a1aaf7742" }
let(:voucher_set_id) { "9d314daa-0878-4b73-922d-698047640cf4" }
let(:vine_api_service) { instance_double(VineApiService) }
before do
allow(VineApiService).to receive(:new).and_return(vine_api_service)
end
describe "#call" do
context "with a valid voucher" do
let!(:vine_connected_app) {
ConnectedApps::Vine.create(
enterprise: distributor, data: { api_key: "1234568", secret: "my_secret" }
)
}
let(:data) {
{
meta: {
responseCode: 200,
limit: 50,
offset: 0,
message: "Redemption successful. This was a test redemption. Do NOT provide " \
"the person with goods or services."
},
data: {
voucher_id: "9d316d27-0dad-411a-8953-316a1aaf7742",
voucher_set_id: "9d314daa-0878-4b73-922d-698047640cf4",
redeemed_by_user_id: 8,
redeemed_by_team_id: 4,
redeemed_amount: 1,
is_test: 1,
updated_at: "2024-10-21T03:07:09.000000Z",
created_at: "2024-10-21T03:07:09.000000Z",
id: 5
}
}.deep_stringify_keys
}
before { add_voucher(vine_voucher) }
it "redeems the voucher with VINE" do
expect(vine_api_service).to receive(:voucher_redemptions)
.with(voucher_id, voucher_set_id, 600)
.and_return(mock_api_response(success: true, data:))
voucher_redeemer_service.call
end
it "closes the linked assement" do
allow(vine_api_service).to receive(:voucher_redemptions)
.and_return(mock_api_response(success: true, data:))
expect {
voucher_redeemer_service.call
}.to change { order.voucher_adjustments.first.state }.to("closed")
end
it "returns true" do
allow(vine_api_service).to receive(:voucher_redemptions)
.and_return(mock_api_response(success: true, data:))
expect(voucher_redeemer_service.call).to be(true)
end
context "when redeeming fails" do
let(:data) {
{
meta: { responseCode: 400, limit: 50, offset: 0, message: "Invalid merchant team." },
data: []
}.deep_stringify_keys
}
before do
allow(vine_api_service).to receive(:voucher_redemptions)
.and_return(mock_api_response(success: false, data:, status: 400))
end
it "doesn't close the linked assement" do
expect {
voucher_redeemer_service.call
}.not_to change { order.voucher_adjustments.first.state }
end
it "returns false" do
expect(voucher_redeemer_service.call).to be(false)
end
it "adds an error message" do
voucher_redeemer_service.call
expect(voucher_redeemer_service.errors).to include(
{ redeeming_failed: "Redeeming the voucher failed" }
)
end
end
end
context "when distributor is not connected to VINE" do
before { add_voucher(vine_voucher) }
it "returns false" do
expect(voucher_redeemer_service.call).to be(false)
end
it "doesn't call the VINE API" do
expect(vine_api_service).not_to receive(:voucher_redemptions)
voucher_redeemer_service.call
end
it "adds an error message" do
voucher_redeemer_service.call
expect(voucher_redeemer_service.errors).to include(
{ vine_settings: "No Vine api settings for the given enterprise" }
)
end
it "doesn't close the linked assement" do
expect {
voucher_redeemer_service.call
}.not_to change { order.voucher_adjustments.first.state }
end
end
# TODO should we set an error or just do nothing ?
context "when there are no voucher added to the order" do
let!(:vine_connected_app) {
ConnectedApps::Vine.create(
enterprise: distributor, data: { api_key: "1234568", secret: "my_secret" }
)
}
it "returns true" do
expect(voucher_redeemer_service.call).to be(true)
end
it "doesn't call the VINE API" do
expect(vine_api_service).not_to receive(:voucher_redemptions)
voucher_redeemer_service.call
end
end
context "with a non vine voucher" do
let!(:vine_connected_app) {
ConnectedApps::Vine.create(
enterprise: distributor, data: { api_key: "1234568", secret: "my_secret" }
)
}
let(:voucher) { create(:voucher_flat_rate, enterprise: distributor) }
before { add_voucher(voucher) }
it "returns true" do
expect(voucher_redeemer_service.call).to be(true)
end
it "doesn't call the VINE API" do
expect(vine_api_service).not_to receive(:voucher_redemptions)
voucher_redeemer_service.call
end
end
context "when there is an API error" do
let!(:vine_connected_app) {
ConnectedApps::Vine.create(
enterprise: distributor, data: { api_key: "1234568", secret: "my_secret" }
)
}
before do
add_voucher(vine_voucher)
allow(vine_api_service).to receive(:voucher_redemptions).and_raise(Faraday::Error)
end
it "returns false" do
expect(voucher_redeemer_service.call).to be(false)
end
it "adds an error message" do
voucher_redeemer_service.call
expect(voucher_redeemer_service.errors).to include(
{ vine_api: "There was an error communicating with the API" }
)
end
it "doesn't close the linked assement" do
expect {
voucher_redeemer_service.call
}.not_to change { order.voucher_adjustments.first.state }
end
it "logs the error and notify bugsnag" do
expect(Rails.logger).to receive(:error)
expect(Bugsnag).to receive(:notify)
voucher_redeemer_service.call
end
end
context "when there is an API authentication error" do
let!(:vine_connected_app) {
ConnectedApps::Vine.create(
enterprise: distributor, data: { api_key: "1234568", secret: "my_secret" }
)
}
let(:data) {
{
meta: { numRecords: 0, totalRows: 0, responseCode: 401,
message: "Incorrect authorization signature." },
data: []
}.deep_stringify_keys
}
before do
add_voucher(vine_voucher)
allow(vine_api_service).to receive(:voucher_redemptions).and_return(
mock_api_response(success: false, status: 401, data: )
)
end
it "returns false" do
expect(voucher_redeemer_service.call).to be(false)
end
it "adds an error message" do
voucher_redeemer_service.call
expect(voucher_redeemer_service.errors).to include(
{ vine_api: "There was an error communicating with the API" }
)
end
it "doesn't close the linked assement" do
expect {
voucher_redeemer_service.call
}.not_to change { order.voucher_adjustments.first.state }
end
end
end
def add_voucher(voucher)
voucher.create_adjustment(voucher.code, order)
VoucherAdjustmentsService.new(order).update
order.update_totals_and_states
end
def mock_api_response(success:, data: nil, status: 200)
mock_response = instance_double(Faraday::Response)
allow(mock_response).to receive(:success?).and_return(success)
allow(mock_response).to receive(:status).and_return(status)
if data.present?
allow(mock_response).to receive(:body).and_return(data)
end
mock_response
end
end