HG-237: Provide msgpack marshalling for invoice templates (#111)

* HG-237: Provide msgpack marshalling for invoice templates

* HG-237: Shut dialyzer whining up with a couple of hotfixes

* HG-237: Try to tame these infernal timeouts
This commit is contained in:
Andrew Mayorov 2017-08-30 14:57:22 +03:00 committed by GitHub
parent 037929640e
commit 356f8b7580
4 changed files with 363 additions and 47 deletions

View File

@ -13,18 +13,32 @@
-define(legacy_invoice(ID, PartyID, ShopID, CreatedAt, Status, Details, Due, Cost, Context, TemplateID),
{domain_Invoice, ID, PartyID, ShopID, CreatedAt, Status, Details, Due, Cost, Context, TemplateID}).
-define(legacy_invoice_paid(),
-define(legacy_invoice_paid(),
{paid, {domain_InvoicePaid}}).
-define(legacy_invoice_unpaid(),
-define(legacy_invoice_unpaid(),
{unpaid, {domain_InvoiceUnpaid}}).
-define(legacy_invoice_cancelled(Reason),
-define(legacy_invoice_cancelled(Reason),
{cancelled, {domain_InvoiceCancelled, Reason}}).
-define(legacy_invoice_fulfilled(Reason),
-define(legacy_invoice_fulfilled(Reason),
{fulfilled, {domain_InvoiceFulfilled, Reason}}).
-define(legacy_invoice_details(Product, Description),
{domain_InvoiceDetails, Product, Description}).
%% Invoice template
-define(legacy_invoice_tpl_created(InvoiceTpl),
{invoice_template_created, {payproc_InvoiceTemplateCreated, InvoiceTpl}}
).
-define(legacy_invoice_tpl_updated(Diff),
{invoice_template_updated, {payproc_InvoiceTemplateUpdated, Diff}}
).
-define(legacy_invoice_tpl_deleted(),
{invoice_template_deleted, {payproc_InvoiceTemplateDeleted}}
).
%% Payment
-define(legacy_payment_started(Payment, RiskScore, Route, Cashflow),
@ -37,7 +51,7 @@
{invoice_payment_session_change,
{payproc_InvoicePaymentSessionChange, Target, Payload}}).
-define(legacy_adjustment_ev(AdjustmentID, Payload),
{invoice_payment_adjustment_change,
{invoice_payment_adjustment_change,
{payproc_InvoicePaymentAdjustmentChange, AdjustmentID, Payload}}).
-define(legacy_payment(ID, CreatedAt, DomainRevision, Status, Payer, Cash, Context),
@ -66,22 +80,22 @@
%% Sessions
-define(legacy_session_started(),
{invoice_payment_session_started,
{invoice_payment_session_started,
{payproc_InvoicePaymentSessionStarted}}).
-define(legacy_session_finished(Result),
{invoice_payment_session_finished,
{invoice_payment_session_finished,
{payproc_InvoicePaymentSessionFinished, Result}}).
-define(legacy_session_suspended(),
{invoice_payment_session_suspended,
{invoice_payment_session_suspended,
{payproc_InvoicePaymentSessionSuspended}}).
-define(legacy_session_activated(),
{invoice_payment_session_activated,
{invoice_payment_session_activated,
{payproc_InvoicePaymentSessionActivated}}).
-define(legacy_trx_bound(Trx),
{invoice_payment_session_transaction_bound,
{payproc_InvoicePaymentSessionTransactionBound, Trx}}).
-define(legacy_proxy_st_changed(ProxySt),
{invoice_payment_session_proxy_state_changed,
{invoice_payment_session_proxy_state_changed,
{payproc_InvoicePaymentSessionProxyStateChanged, ProxySt}}).
-define(legacy_interaction_requested(UserInteraction),
{invoice_payment_session_interaction_requested,
@ -145,5 +159,5 @@
-define(legacy_terminal(ObjectID),
{domain_TerminalRef, ObjectID}).
-endif.

View File

@ -0,0 +1,63 @@
-module(hg_cash_range).
-include_lib("dmsl/include/dmsl_domain_thrift.hrl").
-include("domain.hrl").
-export([marshal/1]).
-export([unmarshal/1]).
-type cash_range() :: dmsl_domain_thrift:'CashRange'().
%% Marshalling
-spec marshal(cash_range()) ->
hg_msgpack_marshalling:value().
marshal(CashRange) ->
marshal(cash_range, CashRange).
marshal(cash_range, #domain_CashRange{
lower = Lower,
upper = Upper
}) ->
[2, [marshal(cash_bound, Lower), marshal(cash_bound, Upper)]];
marshal(cash_bound, {Exclusiveness, Cash}) ->
[marshal(exclusiveness, Exclusiveness), hg_cash:marshal(Cash)];
marshal(exclusiveness, inclusive) ->
<<"inclusive">>;
marshal(exclusiveness, exclusive) ->
<<"exclusive">>.
%% Unmarshalling
-spec unmarshal(hg_msgpack_marshalling:value()) ->
cash_range().
unmarshal(CashRange) ->
unmarshal(cash_range, CashRange).
unmarshal(cash_range, [2, [Lower, Upper]]) ->
#domain_CashRange{
lower = unmarshal(cash_bound, Lower),
upper = unmarshal(cash_bound, Upper)
};
unmarshal(cash_bound, [Exclusiveness, Cash]) ->
{unmarshal(exclusiveness, Exclusiveness), hg_cash:unmarshal(Cash)};
unmarshal(exclusiveness, <<"inclusive">>) ->
inclusive;
unmarshal(exclusiveness, <<"exclusive">>) ->
exclusive;
unmarshal(cash_range, [1, {'domain_CashRange', Upper, Lower}]) ->
#domain_CashRange{
lower = unmarshal(cash_bound_legacy, Lower),
upper = unmarshal(cash_bound_legacy, Upper)
};
unmarshal(cash_bound_legacy, {Exclusiveness, Cash}) when
Exclusiveness == exclusive; Exclusiveness == inclusive
->
{Exclusiveness, hg_cash:unmarshal([1, Cash])}.

View File

@ -136,7 +136,7 @@ call(ID, Args) ->
map_error(hg_machine:call(?NS, {id, ID}, Args)).
get_history(TplID) ->
map_history_error(hg_machine:get_history(?NS, TplID)).
unmarshal(map_history_error(hg_machine:get_history(?NS, TplID))).
map_error({ok, CallResult}) ->
case CallResult of
@ -156,7 +156,7 @@ map_start_error({error, Reason}) ->
error(Reason).
map_history_error({ok, Result}) ->
unwrap_events(Result);
Result;
map_history_error({error, notfound}) ->
throw(#payproc_InvoiceTemplateNotFound{});
map_history_error({error, Reason}) ->
@ -184,12 +184,12 @@ map_history_error({error, Reason}) ->
#payproc_InvoiceTemplateUpdated{diff = Diff}}
).
-define(tpl_deleted,
-define(tpl_deleted(),
{invoice_template_deleted,
#payproc_InvoiceTemplateDeleted{}}
).
assert_invoice_template_not_deleted({_, _, ?ev([?tpl_deleted])}) ->
assert_invoice_template_not_deleted({_, _, [?tpl_deleted()]}) ->
throw(#payproc_InvoiceTemplateRemoved{});
assert_invoice_template_not_deleted(_) ->
ok.
@ -201,11 +201,11 @@ namespace() ->
?NS.
-spec init(tpl_id(), create_params()) ->
hg_machine:result(ev()).
hg_machine:result().
init(ID, Params) ->
Tpl = create_invoice_template(ID, Params),
{wrap_events([?ev([?tpl_created(Tpl)])]), hg_machine_action:new()}.
{[marshal([?tpl_created(Tpl)])], hg_machine_action:new()}.
create_invoice_template(ID, P) ->
#domain_InvoiceTemplate{
@ -219,7 +219,7 @@ create_invoice_template(ID, P) ->
}.
-spec process_signal(hg_machine:signal(), hg_machine:history(ev())) ->
hg_machine:result(ev()).
hg_machine:result().
process_signal(timeout, _History) ->
signal_no_changes();
@ -228,37 +228,37 @@ process_signal({repair, _}, _History) ->
signal_no_changes().
signal_no_changes() ->
{wrap_events([?ev([])]), hg_machine_action:new()}.
{[], hg_machine_action:new()}.
-spec process_call(call(), hg_machine:history(ev())) ->
{hg_machine:response(), hg_machine:result(ev())}.
{hg_machine:response(), hg_machine:result()}.
process_call(Call, History) ->
Tpl = collapse_history(unwrap_events(History)),
{Response, Event} = handle_call(Call, Tpl),
{{ok, Response}, {wrap_events([Event]), hg_machine_action:new()}}.
Tpl = collapse_history(unmarshal(History)),
{Response, Changes} = handle_call(Call, Tpl),
{{ok, Response}, {[marshal(Changes)], hg_machine_action:new()}}.
handle_call({update, Params}, Tpl) ->
Event = ?ev([?tpl_updated(Params)]),
{merge_event(Event, Tpl), Event};
Changes = [?tpl_updated(Params)],
{merge_changes(Changes, Tpl), Changes};
handle_call(delete, _Tpl) ->
{ok, ?ev([?tpl_deleted])}.
{ok, [?tpl_deleted()]}.
collapse_history(History) ->
lists:foldl(
fun ({_ID, _, Ev}, Tpl) -> merge_event(Ev, Tpl) end,
fun ({_ID, _, Ev}, Tpl) -> merge_changes(Ev, Tpl) end,
undefined,
History
).
merge_event(?ev([?tpl_created(Tpl)]), _) ->
merge_changes([?tpl_created(Tpl)], _) ->
Tpl;
merge_event(?ev([?tpl_updated(#payproc_InvoiceTemplateUpdateParams{
merge_changes([?tpl_updated(#payproc_InvoiceTemplateUpdateParams{
details = Details,
invoice_lifetime = InvoiceLifetime,
cost = Cost,
context = Context
})]), Tpl) ->
})], Tpl) ->
Diff = [
{details, Details},
{invoice_lifetime, InvoiceLifetime},
@ -279,27 +279,266 @@ update_field({context, V}, Tpl) ->
Tpl#domain_InvoiceTemplate{context = V}.
%% Event provider
-type msgpack_value() :: dmsl_msgpack_thrift:'Value'().
-spec publish_event(tpl_id(), msgpack_value()) ->
hg_event_provider:public_event().
publish_event(ID, Event) ->
{{invoice_template_id, ID}, unmarshal(Event)}.
publish_event(ID, Changes) ->
{{invoice_template_id, ID}, ?ev(unmarshal({list, change}, Changes))}.
%%
unwrap_event({ID, Dt, Payload}) ->
{ID, Dt, unmarshal(Payload)}.
-include("legacy_structures.hrl").
unwrap_events(History) ->
[unwrap_event(E) || E <- History].
marshal(Changes) when is_list(Changes) ->
[marshal(change, Change) || Change <- Changes].
wrap_events(Events) ->
[marshal(E) || E <- Events].
marshal(change, ?tpl_created(InvoiceTpl)) ->
[2, #{
<<"change">> => <<"created">>,
<<"tpl">> => marshal(invoice_template, InvoiceTpl)
}];
marshal(change, ?tpl_updated(Diff)) ->
[2, #{
<<"change">> => <<"updated">>,
<<"diff">> => marshal(invoice_template_diff, Diff)
}];
marshal(change, ?tpl_deleted()) ->
[2, #{
<<"change">> => <<"deleted">>
}];
marshal(V) ->
{bin, term_to_binary(V)}.
marshal(invoice_template, #domain_InvoiceTemplate{} = InvoiceTpl) ->
genlib_map:compact(#{
<<"id">> => marshal(str, InvoiceTpl#domain_InvoiceTemplate.id),
<<"shop_id">> => marshal(str, InvoiceTpl#domain_InvoiceTemplate.shop_id),
<<"owner_id">> => marshal(str, InvoiceTpl#domain_InvoiceTemplate.owner_id),
<<"details">> => marshal(details, InvoiceTpl#domain_InvoiceTemplate.details),
<<"lifetime">> => marshal(lifetime, InvoiceTpl#domain_InvoiceTemplate.invoice_lifetime),
<<"cost">> => marshal(cost, InvoiceTpl#domain_InvoiceTemplate.cost),
<<"context">> => hg_content:marshal(InvoiceTpl#domain_InvoiceTemplate.context)
});
unmarshal({bin, B}) ->
binary_to_term(B).
marshal(invoice_template_diff, #payproc_InvoiceTemplateUpdateParams{} = Diff) ->
genlib_map:compact(#{
<<"details">> => marshal(details, Diff#payproc_InvoiceTemplateUpdateParams.details),
<<"lifetime">> => marshal(lifetime, Diff#payproc_InvoiceTemplateUpdateParams.invoice_lifetime),
<<"cost">> => marshal(cost, Diff#payproc_InvoiceTemplateUpdateParams.cost),
<<"context">> => hg_content:marshal(Diff#payproc_InvoiceTemplateUpdateParams.context)
});
marshal(details, #domain_InvoiceDetails{} = Details) ->
genlib_map:compact(#{
<<"product">> => marshal(str, Details#domain_InvoiceDetails.product),
<<"description">> => marshal(str, Details#domain_InvoiceDetails.description),
<<"cart">> => marshal(cart, Details#domain_InvoiceDetails.cart)
});
marshal(cart, #domain_InvoiceCart{lines = Lines}) ->
[marshal(line, Line) || Line <- Lines];
marshal(line, #domain_InvoiceLine{} = InvoiceLine) ->
#{
<<"product">> => marshal(str, InvoiceLine#domain_InvoiceLine.product),
<<"quantity">> => marshal(int, InvoiceLine#domain_InvoiceLine.quantity),
<<"price">> => hg_cash:marshal(InvoiceLine#domain_InvoiceLine.price),
<<"metadata">> => marshal(metadata, InvoiceLine#domain_InvoiceLine.metadata)
};
marshal(lifetime, #domain_LifetimeInterval{years = Years, months = Months, days = Days}) ->
genlib_map:compact(#{
<<"years">> => marshal(int, Years),
<<"months">> => marshal(int, Months),
<<"days">> => marshal(int, Days)
});
marshal(cost, {fixed, Cash}) ->
[<<"fixed">>, hg_cash:marshal(Cash)];
marshal(cost, {range, CashRange}) ->
[<<"range">>, hg_cash_range:marshal(CashRange)];
marshal(cost, {unlim, _}) ->
<<"unlim">>;
marshal(metadata, Metadata) ->
maps:fold(
fun(K, V, Acc) ->
maps:put(marshal(str, K), hg_msgpack_marshalling:unmarshal(V), Acc)
end,
#{},
Metadata
);
marshal(_, Other) ->
Other.
%%
unmarshal(Events) when is_list(Events) ->
[unmarshal(Event) || Event <- Events];
unmarshal({ID, Dt, Payload}) ->
{ID, Dt, unmarshal({list, change}, Payload)}.
%% Version > 1
unmarshal({list, change}, Changes) when is_list(Changes) ->
[unmarshal(change, Change) || Change <- Changes];
%% Version 1
unmarshal({list, change}, {bin, Bin}) when is_binary(Bin) ->
?ev(Changes) = binary_to_term(Bin),
[unmarshal(change, [1, Change]) || Change <- Changes];
unmarshal(change, [2, #{
<<"change">> := <<"created">>,
<<"tpl">> := InvoiceTpl
}]) ->
?tpl_created(unmarshal(invoice_template, InvoiceTpl));
unmarshal(change, [2, #{
<<"change">> := <<"updated">>,
<<"diff">> := Diff
}]) ->
?tpl_updated(unmarshal(invoice_template_diff, Diff));
unmarshal(change, [2, #{
<<"change">> := <<"deleted">>
}]) ->
?tpl_deleted();
unmarshal(change, [1, ?legacy_invoice_tpl_created(InvoiceTpl)]) ->
?tpl_created(unmarshal(invoice_template_legacy, InvoiceTpl));
unmarshal(change, [1, ?legacy_invoice_tpl_updated(Diff)]) ->
?tpl_updated(unmarshal(invoice_template_diff_legacy, Diff));
unmarshal(change, [1, ?legacy_invoice_tpl_deleted()]) ->
?tpl_deleted();
unmarshal(invoice_template, #{
<<"id">> := ID,
<<"shop_id">> := ShopID,
<<"owner_id">> := OwnerID,
<<"details">> := Details,
<<"lifetime">> := Lifetime,
<<"cost">> := Cost
} = V) ->
#domain_InvoiceTemplate{
id = unmarshal(str, ID),
shop_id = unmarshal(str, ShopID),
owner_id = unmarshal(str, OwnerID),
details = unmarshal(details, Details),
invoice_lifetime = unmarshal(lifetime, Lifetime),
cost = unmarshal(cost, Cost),
context = hg_content:unmarshal(genlib_map:get(<<"context">>, V))
};
unmarshal(invoice_template_diff, #{} = V) ->
#payproc_InvoiceTemplateUpdateParams{
details = unmarshal(details, genlib_map:get(<<"details">>, V)),
invoice_lifetime = unmarshal(lifetime, genlib_map:get(<<"lifetime">>, V)),
cost = unmarshal(cost, genlib_map:get(<<"cost">>, V)),
context = hg_content:unmarshal(genlib_map:get(<<"context">>, V))
};
unmarshal(invoice_template_diff_legacy, {payproc_InvoiceTemplateUpdateParams,
Details,
Lifetime,
Cost,
Context
}) ->
#payproc_InvoiceTemplateUpdateParams{
details = unmarshal(details_legacy, Details),
invoice_lifetime = unmarshal(lifetime_legacy, Lifetime),
cost = unmarshal(cost_legacy, Cost),
context = hg_content:unmarshal(Context)
};
unmarshal(invoice_template_legacy, {domain_InvoiceTemplate,
ID,
OwnerID,
ShopID,
Details,
Lifetime,
Cost,
Context
}) ->
#domain_InvoiceTemplate{
id = unmarshal(str, ID),
shop_id = unmarshal(str, ShopID),
owner_id = unmarshal(str, OwnerID),
details = unmarshal(details_legacy, Details),
invoice_lifetime = unmarshal(lifetime_legacy, Lifetime),
cost = unmarshal(cost_legacy, Cost),
context = hg_content:unmarshal(Context)
};
unmarshal(details, #{<<"product">> := Product} = Details) ->
Description = maps:get(<<"description">>, Details, undefined),
Cart = maps:get(<<"cart">>, Details, undefined),
#domain_InvoiceDetails{
product = unmarshal(str, Product),
description = unmarshal(str, Description),
cart = unmarshal(cart, Cart)
};
unmarshal(details_legacy, ?legacy_invoice_details(Product, Description)) ->
#domain_InvoiceDetails{
product = unmarshal(str, Product),
description = unmarshal(str, Description)
};
unmarshal(cart, Lines) when is_list(Lines) ->
#domain_InvoiceCart{lines = [unmarshal(line, Line) || Line <- Lines]};
unmarshal(line, #{
<<"product">> := Product,
<<"quantity">> := Quantity,
<<"price">> := Price,
<<"metadata">> := Metadata
}) ->
#domain_InvoiceLine{
product = unmarshal(str, Product),
quantity = unmarshal(int, Quantity),
price = hg_cash:unmarshal(Price),
metadata = unmarshal(metadata, Metadata)
};
unmarshal(lifetime, #{} = V) ->
#domain_LifetimeInterval{
years = unmarshal(int, genlib_map:get(<<"years">>, V)),
months = unmarshal(int, genlib_map:get(<<"months">>, V)),
days = unmarshal(int, genlib_map:get(<<"days">>, V))
};
unmarshal(lifetime_legacy, {domain_LifetimeInterval, Years, Months, Days}) ->
#domain_LifetimeInterval{
years = unmarshal(int, Years),
months = unmarshal(int, Months),
days = unmarshal(int, Days)
};
unmarshal(cost, [<<"fixed">>, Cash]) ->
{fixed, hg_cash:unmarshal(Cash)};
unmarshal(cost, [<<"range">>, CashRange]) ->
{range, hg_cash_range:unmarshal(CashRange)};
unmarshal(cost, <<"unlim">>) ->
{unlim, #domain_InvoiceTemplateCostUnlimited{}};
unmarshal(cost_legacy, {fixed, Cash}) ->
{fixed, hg_cash:unmarshal([1, Cash])};
unmarshal(cost_legacy, {range, CashRange}) ->
{range, hg_cash_range:unmarshal([1, CashRange])};
unmarshal(cost_legacy, {unlim, _}) ->
{unlim, #domain_InvoiceTemplateCostUnlimited{}};
unmarshal(metadata, Metadata) ->
maps:fold(
fun(K, V, Acc) ->
maps:put(unmarshal(str, K), hg_msgpack_marshalling:marshal(V), Acc)
end,
#{},
Metadata
);
unmarshal(_, Other) ->
Other.

View File

@ -885,7 +885,7 @@ payment_hold_auto_cancellation(C) ->
[
?payment_ev(PaymentID, ?session_ev(?cancelled(), ?session_finished(?session_succeeded()))),
?payment_ev(PaymentID, ?payment_status_changed(?cancelled()))
] = next_event(InvoiceID, 1500, Client),
] = next_event(InvoiceID, 3000, Client),
?invoice_state(
?invoice_w_status(?invoice_unpaid()),
[?payment_state(?payment_w_status(PaymentID, ?cancelled()))]
@ -927,7 +927,7 @@ payment_hold_auto_capturing(C) ->
?payment_ev(PaymentID, ?session_ev(?captured(), ?session_finished(?session_succeeded()))),
?payment_ev(PaymentID, ?payment_status_changed(?captured())),
?invoice_status_changed(?invoice_paid())
] = next_event(InvoiceID, 1500, Client).
] = next_event(InvoiceID, 3000, Client).
%%
@ -1218,7 +1218,7 @@ construct_domain_fixture() ->
hold_lifetime = {decisions, [
#domain_HoldLifetimeDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, #domain_HoldLifetime{seconds = 3}}
then_ = {value, #domain_HoldLifetime{seconds = 2}}
}
]}
}
@ -1278,7 +1278,7 @@ construct_domain_fixture() ->
hold_lifetime = {decisions, [
#domain_HoldLifetimeDecision{
if_ = {condition, {currency_is, ?cur(<<"RUB">>)}},
then_ = {value, #domain_HoldLifetime{seconds = 3}}
then_ = {value, #domain_HoldLifetime{seconds = 2}}
}
]}
}
@ -1658,7 +1658,7 @@ construct_domain_fixture() ->
account = AccountRUB,
risk_coverage = low,
payment_flow = {hold, #domain_TerminalPaymentFlowHold{
hold_lifetime = #domain_HoldLifetime{seconds = 3}
hold_lifetime = #domain_HoldLifetime{seconds = 2}
}}
}
}},