mirror of
https://github.com/valitydev/hellgate.git
synced 2024-11-06 10:55:22 +00:00
MSPF-456: Processing deadline (#352)
* Added payment deadlines * Actualized code to match damsel, added deadline check * Rewrote deadline check, added env deadline support * Removed deadline creation * Moved and renamed processing deadline validation * Removed extra clause * Codestyle fixes * Handle payment_deadline_reached failure * Added processing deadline test * Added tests for different flows * Uncommented tests * Removed unnecessary sleeps from tests * Check target type in validate_processing_deadline * Stop session processing after deadline validation * Fix test * Review fixes * Fixed a bunch of tests
This commit is contained in:
parent
83925e1856
commit
137ceb9e66
@ -283,9 +283,10 @@ init_(PaymentID, Params, Opts) ->
|
||||
MerchantTerms = get_merchant_terms(Opts, Revision),
|
||||
VS1 = collect_validation_varset(Party, Shop, VS0),
|
||||
Context = get_context_params(Params),
|
||||
Deadline = get_processing_deadline(Params),
|
||||
Payment = construct_payment(
|
||||
PaymentID, CreatedAt, Cost, Payer, Flow, MerchantTerms, Party, Shop,
|
||||
VS1, Revision, MakeRecurrent, Context, ExternalID
|
||||
VS1, Revision, MakeRecurrent, Context, ExternalID, Deadline
|
||||
),
|
||||
Events = [?payment_started(Payment)],
|
||||
{collapse_changes(Events, undefined), {Events, hg_machine_action:instant()}}.
|
||||
@ -333,6 +334,9 @@ get_context_params(#payproc_InvoicePaymentParams{context = Context}) ->
|
||||
get_external_id(#payproc_InvoicePaymentParams{external_id = ExternalID}) ->
|
||||
ExternalID.
|
||||
|
||||
get_processing_deadline(#payproc_InvoicePaymentParams{processing_deadline = Deadline}) ->
|
||||
Deadline.
|
||||
|
||||
construct_payer({payment_resource, #payproc_PaymentResourcePayerParams{
|
||||
resource = Resource,
|
||||
contact_info = ContactInfo
|
||||
@ -401,7 +405,8 @@ construct_payment(
|
||||
Revision,
|
||||
MakeRecurrent,
|
||||
Context,
|
||||
ExternalID
|
||||
ExternalID,
|
||||
Deadline
|
||||
) ->
|
||||
#domain_TermSet{payments = PaymentTerms, recurrent_paytools = RecurrentTerms} = Terms,
|
||||
PaymentTool = get_payer_payment_tool(Payer),
|
||||
@ -437,19 +442,20 @@ construct_payment(
|
||||
},
|
||||
ok = validate_recurrent_intention(RecurrentValidationVarset, MakeRecurrent),
|
||||
#domain_InvoicePayment{
|
||||
id = PaymentID,
|
||||
created_at = CreatedAt,
|
||||
owner_id = Party#domain_Party.id,
|
||||
shop_id = Shop#domain_Shop.id,
|
||||
domain_revision = Revision,
|
||||
party_revision = Party#domain_Party.revision,
|
||||
status = ?pending(),
|
||||
cost = Cost,
|
||||
payer = Payer,
|
||||
flow = Flow,
|
||||
make_recurrent = MakeRecurrent,
|
||||
context = Context,
|
||||
external_id = ExternalID
|
||||
id = PaymentID,
|
||||
created_at = CreatedAt,
|
||||
owner_id = Party#domain_Party.id,
|
||||
shop_id = Shop#domain_Shop.id,
|
||||
domain_revision = Revision,
|
||||
party_revision = Party#domain_Party.revision,
|
||||
status = ?pending(),
|
||||
cost = Cost,
|
||||
payer = Payer,
|
||||
flow = Flow,
|
||||
make_recurrent = MakeRecurrent,
|
||||
context = Context,
|
||||
external_id = ExternalID,
|
||||
processing_deadline = Deadline
|
||||
}.
|
||||
|
||||
construct_payment_flow({instant, _}, _CreatedAt, _Terms, _VS, _Revision) ->
|
||||
@ -963,6 +969,19 @@ assert_capture_cost_currency(?cash(_, PassedSymCode), #domain_InvoicePayment{cos
|
||||
passed_currency = PassedSymCode
|
||||
}).
|
||||
|
||||
validate_processing_deadline(#domain_InvoicePayment{processing_deadline = Deadline}, _TargetType = processed) ->
|
||||
case hg_invoice_utils:check_deadline(Deadline) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, deadline_reached} ->
|
||||
{failure, payproc_errors:construct('PaymentFailure',
|
||||
{authorization_failed, {processing_deadline_reached, #payprocerr_GeneralFailure{}}}
|
||||
)}
|
||||
end;
|
||||
validate_processing_deadline(_, _TargetType) ->
|
||||
ok.
|
||||
|
||||
|
||||
assert_capture_cart(_Cost, undefined) ->
|
||||
ok;
|
||||
assert_capture_cart(Cost, Cart) ->
|
||||
@ -1573,10 +1592,15 @@ process_session(Action, St) ->
|
||||
process_session(Session, Action, St).
|
||||
|
||||
process_session(undefined, Action, St0) ->
|
||||
Events = start_session(get_target(St0)),
|
||||
St1 = collapse_changes(Events, St0),
|
||||
Session = get_activity_session(St1),
|
||||
process_session(Session, Action, Events, St1);
|
||||
case validate_processing_deadline(get_payment(St0), get_target_type(get_target(St0))) of
|
||||
ok ->
|
||||
Events = start_session(get_target(St0)),
|
||||
St1 = collapse_changes(Events, St0),
|
||||
Result = {start_session(get_target(St0)), hg_machine_action:set_timeout(0, Action)},
|
||||
finish_session_processing(Result, St1);
|
||||
Failure ->
|
||||
process_failure(get_activity(St0), [], Action, Failure, St0)
|
||||
end;
|
||||
process_session(Session, Action, St) ->
|
||||
process_session(Session, Action, [], St).
|
||||
|
||||
@ -2025,7 +2049,8 @@ construct_proxy_payment(
|
||||
created_at = CreatedAt,
|
||||
payer = Payer,
|
||||
cost = Cost,
|
||||
make_recurrent = MakeRecurrent
|
||||
make_recurrent = MakeRecurrent,
|
||||
processing_deadline = Deadline
|
||||
},
|
||||
Trx
|
||||
) ->
|
||||
@ -2037,7 +2062,8 @@ construct_proxy_payment(
|
||||
payment_resource = construct_payment_resource(Payer),
|
||||
cost = construct_proxy_cash(Cost),
|
||||
contact_info = ContactInfo,
|
||||
make_recurrent = MakeRecurrent
|
||||
make_recurrent = MakeRecurrent,
|
||||
processing_deadline = Deadline
|
||||
}.
|
||||
|
||||
construct_payment_resource(?payment_resource_payer(Resource, _)) ->
|
||||
|
@ -14,6 +14,7 @@
|
||||
-export([assert_shop_operable/1]).
|
||||
-export([compute_shop_terms/4]).
|
||||
-export([get_cart_amount/1]).
|
||||
-export([check_deadline/1]).
|
||||
|
||||
-type amount() :: dmsl_domain_thrift:'Amount'().
|
||||
-type currency() :: dmsl_domain_thrift:'CurrencyRef'().
|
||||
@ -132,3 +133,14 @@ get_line_amount(#domain_InvoiceLine{
|
||||
}) ->
|
||||
#domain_Cash{amount = Amount * Quantity, currency = Currency}.
|
||||
|
||||
-spec check_deadline(Deadline :: binary() | undefined) ->
|
||||
ok | {error, deadline_reached}.
|
||||
check_deadline(undefined) ->
|
||||
ok;
|
||||
check_deadline(Deadline) ->
|
||||
case hg_datetime:compare(Deadline, hg_datetime:format_now()) of
|
||||
later ->
|
||||
ok;
|
||||
_ ->
|
||||
{error, deadline_reached}
|
||||
end.
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
-export([payment_start_idempotency/1]).
|
||||
-export([payment_success/1]).
|
||||
-export([processing_deadline_reached_test/1]).
|
||||
-export([payment_success_empty_cvv/1]).
|
||||
-export([payment_success_additional_info/1]).
|
||||
-export([payment_w_terminal_success/1]).
|
||||
@ -64,6 +65,7 @@
|
||||
-export([payment_hold_cancellation/1]).
|
||||
-export([payment_hold_auto_cancellation/1]).
|
||||
-export([payment_hold_capturing/1]).
|
||||
-export([deadline_doesnt_affect_payment_capturing/1]).
|
||||
-export([payment_hold_partial_capturing/1]).
|
||||
-export([payment_hold_partial_capturing_with_cart/1]).
|
||||
-export([payment_hold_partial_capturing_with_cart_missing_cash/1]).
|
||||
@ -76,6 +78,7 @@
|
||||
-export([invalid_refund_shop_status/1]).
|
||||
-export([payment_refund_idempotency/1]).
|
||||
-export([payment_refund_success/1]).
|
||||
-export([deadline_doesnt_affect_payment_refund/1]).
|
||||
-export([payment_manual_refund/1]).
|
||||
-export([payment_partial_refunds_success/1]).
|
||||
-export([payment_refund_id_types/1]).
|
||||
@ -198,6 +201,7 @@ groups() ->
|
||||
|
||||
payment_start_idempotency,
|
||||
payment_success,
|
||||
processing_deadline_reached_test,
|
||||
payment_success_empty_cvv,
|
||||
payment_success_additional_info,
|
||||
payment_w_terminal_success,
|
||||
@ -231,6 +235,7 @@ groups() ->
|
||||
retry_temporary_unavailability_refund,
|
||||
payment_refund_idempotency,
|
||||
payment_refund_success,
|
||||
deadline_doesnt_affect_payment_refund,
|
||||
payment_partial_refunds_success,
|
||||
invalid_amount_payment_partial_refund,
|
||||
invalid_amount_partial_capture_and_refund,
|
||||
@ -246,6 +251,7 @@ groups() ->
|
||||
payment_hold_cancellation,
|
||||
payment_hold_auto_cancellation,
|
||||
payment_hold_capturing,
|
||||
deadline_doesnt_affect_payment_capturing,
|
||||
invalid_currency_partial_capture,
|
||||
invalid_amount_partial_capture,
|
||||
payment_hold_partial_capturing,
|
||||
@ -805,6 +811,27 @@ payment_success(C) ->
|
||||
?payment_w_status(PaymentID, ?captured()) = Payment,
|
||||
?payment_w_context(Context) = Payment.
|
||||
|
||||
-spec processing_deadline_reached_test(config()) -> test_return().
|
||||
|
||||
processing_deadline_reached_test(C) ->
|
||||
Client = cfg(client, C),
|
||||
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
|
||||
Context = #'Content'{
|
||||
type = <<"application/x-erlang-binary">>,
|
||||
data = erlang:term_to_binary({you, 643, "not", [<<"welcome">>, here]})
|
||||
},
|
||||
PaymentParams0 = set_payment_context(Context, make_payment_params()),
|
||||
Deadline = hg_datetime:format_now(),
|
||||
PaymentParams = PaymentParams0#payproc_InvoicePaymentParams{processing_deadline = Deadline},
|
||||
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
|
||||
PaymentID = await_sessions_restarts(PaymentID, ?processed(), InvoiceID, Client, 0),
|
||||
[?payment_ev(PaymentID, ?payment_status_changed(?failed({failure, Failure})))] = next_event(InvoiceID, Client),
|
||||
ok = payproc_errors:match(
|
||||
'PaymentFailure',
|
||||
Failure,
|
||||
fun({authorization_failed, {processing_deadline_reached, _}}) -> ok end
|
||||
).
|
||||
|
||||
-spec payment_success_empty_cvv(config()) -> test_return().
|
||||
|
||||
payment_success_empty_cvv(C) ->
|
||||
@ -1774,6 +1801,55 @@ payment_refund_success(C) ->
|
||||
?invalid_payment_status(?refunded()) =
|
||||
hg_client_invoicing:refund_payment(InvoiceID, PaymentID, RefundParams, Client).
|
||||
|
||||
-spec deadline_doesnt_affect_payment_refund(config()) -> _ | no_return().
|
||||
|
||||
deadline_doesnt_affect_payment_refund(C) ->
|
||||
Client = cfg(client, C),
|
||||
PartyClient = cfg(party_client, C),
|
||||
ShopID = hg_ct_helper:create_battle_ready_shop(?cat(2), <<"RUB">>, ?tmpl(2), ?pinst(2), PartyClient),
|
||||
InvoiceID = start_invoice(ShopID, <<"rubberduck">>, make_due_date(10), 42000, C),
|
||||
ProcessingDeadline = 4000, % ms
|
||||
PaymentParams = set_processing_deadline(ProcessingDeadline, make_payment_params()),
|
||||
PaymentID = process_payment(InvoiceID, PaymentParams, Client),
|
||||
RefundParams = make_refund_params(),
|
||||
% not finished yet
|
||||
?invalid_payment_status(?processed()) =
|
||||
hg_client_invoicing:refund_payment(InvoiceID, PaymentID, RefundParams, Client),
|
||||
PaymentID = await_payment_capture(InvoiceID, PaymentID, Client),
|
||||
timer:sleep(ProcessingDeadline),
|
||||
% not enough funds on the merchant account
|
||||
Failure = {failure, payproc_errors:construct('RefundFailure',
|
||||
{terms_violated, {insufficient_merchant_funds, #payprocerr_GeneralFailure{}}}
|
||||
)},
|
||||
Refund0 = #domain_InvoicePaymentRefund{id = RefundID0} =
|
||||
hg_client_invoicing:refund_payment(InvoiceID, PaymentID, RefundParams, Client),
|
||||
PaymentID = refund_payment(InvoiceID, PaymentID, RefundID0, Refund0, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?refund_ev(RefundID0, ?refund_status_changed(?refund_failed(Failure))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
% top up merchant account
|
||||
InvoiceID2 = start_invoice(ShopID, <<"rubberduck">>, make_due_date(10), 42000, C),
|
||||
PaymentID2 = process_payment(InvoiceID2, make_payment_params(), Client),
|
||||
PaymentID2 = await_payment_capture(InvoiceID2, PaymentID2, Client),
|
||||
% create a refund finally
|
||||
Refund = #domain_InvoicePaymentRefund{id = RefundID} =
|
||||
hg_client_invoicing:refund_payment(InvoiceID, PaymentID, RefundParams, Client),
|
||||
Refund =
|
||||
hg_client_invoicing:get_payment_refund(InvoiceID, PaymentID, RefundID, Client),
|
||||
PaymentID = refund_payment(InvoiceID, PaymentID, RefundID, Refund, Client),
|
||||
PaymentID = await_refund_session_started(InvoiceID, PaymentID, RefundID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?refund_ev(ID, ?session_ev(?refunded(), ?trx_bound(_)))),
|
||||
?payment_ev(PaymentID, ?refund_ev(ID, ?session_ev(?refunded(), ?session_finished(?session_succeeded()))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?refund_ev(ID, ?refund_status_changed(?refund_succeeded()))),
|
||||
?payment_ev(PaymentID, ?payment_status_changed(?refunded()))
|
||||
] = next_event(InvoiceID, Client),
|
||||
#domain_InvoicePaymentRefund{status = ?refund_succeeded()} =
|
||||
hg_client_invoicing:get_payment_refund(InvoiceID, PaymentID, RefundID, Client).
|
||||
|
||||
|
||||
-spec payment_manual_refund(config()) -> _ | no_return().
|
||||
|
||||
payment_manual_refund(C) ->
|
||||
@ -2147,8 +2223,19 @@ payment_hold_auto_cancellation(C) ->
|
||||
payment_hold_capturing(C) ->
|
||||
Client = cfg(client, C),
|
||||
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
|
||||
PaymentParams = make_payment_params({hold, cancel}),
|
||||
PaymentID = process_payment(InvoiceID, make_payment_params({hold, cancel}), Client),
|
||||
ok = hg_client_invoicing:capture_payment(InvoiceID, PaymentID, <<"ok">>, Client),
|
||||
PaymentID = await_payment_capture(InvoiceID, PaymentID, <<"ok">>, Client).
|
||||
|
||||
-spec deadline_doesnt_affect_payment_capturing(config()) -> _ | no_return().
|
||||
|
||||
deadline_doesnt_affect_payment_capturing(C) ->
|
||||
Client = cfg(client, C),
|
||||
InvoiceID = start_invoice(<<"rubberduck">>, make_due_date(10), 42000, C),
|
||||
ProcessingDeadline = 4000, % ms
|
||||
PaymentParams = set_processing_deadline(ProcessingDeadline, make_payment_params({hold, cancel})),
|
||||
PaymentID = process_payment(InvoiceID, PaymentParams, Client),
|
||||
timer:sleep(ProcessingDeadline),
|
||||
ok = hg_client_invoicing:capture_payment(InvoiceID, PaymentID, <<"ok">>, Client),
|
||||
PaymentID = await_payment_capture(InvoiceID, PaymentID, <<"ok">>, Client).
|
||||
|
||||
@ -2435,9 +2522,11 @@ adhoc_repair_failed_succeeded(C) ->
|
||||
PaymentParams = make_payment_params(PaymentTool, Session),
|
||||
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
|
||||
] = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
% assume no more events here since machine is FUBAR already
|
||||
timeout = next_event(InvoiceID, 2000, Client),
|
||||
Changes = [
|
||||
@ -2477,7 +2566,9 @@ adhoc_repair_invalid_changes_failed(C) ->
|
||||
PaymentParams = make_payment_params(PaymentTool, Session),
|
||||
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
|
||||
] = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
timeout = next_event(InvoiceID, 1000, Client),
|
||||
@ -2652,7 +2743,9 @@ repair_fail_session_succeeded(C) ->
|
||||
PaymentParams = make_payment_params(PaymentTool, Session),
|
||||
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
|
||||
] = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
|
||||
@ -2701,7 +2794,9 @@ repair_complex_succeeded_second(C) ->
|
||||
PaymentParams = make_payment_params(PaymentTool, Session),
|
||||
PaymentID = start_payment(InvoiceID, PaymentParams, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
|
||||
] = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?trx_bound(?trx_info(PaymentID))))
|
||||
] = next_event(InvoiceID, Client),
|
||||
|
||||
@ -3059,10 +3154,14 @@ await_payment_session_started(InvoiceID, PaymentID, Client, Target) ->
|
||||
PaymentID.
|
||||
|
||||
await_payment_process_interaction(InvoiceID, PaymentID, Client) ->
|
||||
Events0 = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started()))
|
||||
] = Events0,
|
||||
Events1 = next_event(InvoiceID, Client),
|
||||
[
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?session_started())),
|
||||
?payment_ev(PaymentID, ?session_ev(?processed(), ?interaction_requested(UserInteraction)))
|
||||
] = next_event(InvoiceID, Client),
|
||||
] = Events1,
|
||||
UserInteraction.
|
||||
|
||||
await_payment_process_finish(InvoiceID, PaymentID, Client) ->
|
||||
@ -4580,3 +4679,8 @@ construct_term_set_for_partial_capture_provider_permit(Revision) ->
|
||||
}
|
||||
}}
|
||||
].
|
||||
|
||||
% Deadline as timeout()
|
||||
set_processing_deadline(Timeout, PaymentParams) ->
|
||||
Deadline = woody_deadline:to_binary(woody_deadline:from_timeout(Timeout)),
|
||||
PaymentParams#payproc_InvoicePaymentParams{processing_deadline = Deadline}.
|
||||
|
Loading…
Reference in New Issue
Block a user