The issue originally arises from JoinAcross' KeyCollisionFunction not accommodating merging:
(note "ignored" columns are omitted here as these are red herrings I should have originally omitted in the question [but which don't change the relevance of answers] - also prepending "L-" "R-" satisfies Dataset's current depth needs in order to format)
fixed = {"f1", "f2"};
varying = {"v1", "v2"};
d1N = d1[All, Join[fixed, varying]];
d2N = d2[All, Join[fixed, varying]];
joined = JoinAcross[d1N, d2N, fixed,"KeyCollisionFunction" -> ({"L-" <> #, "R-" <> #} &)]

The difficulty is that KeyCollision's function separates the values of both colliding keys into left ( L-_ ) and right ( R-_ ) columns thereby preventing you from getting at them simultaneously in the initial sweep. Perhaps this is deliberate ("non-joined keys in JoinAcross" are considered to "collide" as opposed to being "mergable") or perhaps merging is slated for later releases; at any rate we can get at both values in a subsequent, "merging sweep" (albeit less efficiently than desirable).
Now we could sweep across each row coalescing each ( L-_ ) and ( R-_ ) pair while sweeping down or possibly more efficiently by doing this coalescing once by first transposing. A helper Collapse function collapses a specified pair, Fold does the job for all pairs before transposing back - code for which appears at the post's end:

We have called this implementation MyJoinAcrossMerge so that as originally requested we have
MyJoinAcrossMerge[{d1N, d2N}, fixed, varying, foo ]

This usage really does however, seem to fall more naturally into JoinAcross' syntax so for future reference we'll re-define a similar version that automatically finds the "varying" keys (the "non-fixed" keys common to both) while relabelling with a KeyCoalescingFunction that seems to better connote this broader capture:
MyJoinAcross[d1_, d2_, fixed_, "KeyCoalescingFunction" -> f_] :=Where[
varying = Normal@Complement[Join @@ (Keys@First@# & /@ {d1, d2}), fixed],
MyJoinAcrossMerge[{d1, d2}, fixed, varying, f ]];
We can do a similar framing for both Kuba's and alancalvitti's answers to yield alternative versions KyJoinAcross and CyJoinAcross (see the post's end for details) before checking timings:
{{Spacer@50, d1N, Spacer@30, d2N} // Row,
"",
MyJoinAcross[d1N, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
KyJoinAcross[d1N, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
CyJoinAcross[d1N, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
CyJoinAcross[d1N, d2N, fixed, "KeyCoalescingFunction" -> List ] //RepeatedTiming
} // Column

while noting slightly different semantics exemplified here when the first dataset contains a repeated row:
{{Spacer@45, d1Na = Insert[d1N, d1N[[3]], 3], Spacer@20, d2N} // Row,
"",
MyJoinAcross[d1Na, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
KyJoinAcross[d1Na, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
CyJoinAcross[d1Na, d2N, fixed, "KeyCoalescingFunction" -> foo ] //RepeatedTiming,
CyJoinAcross[d1Na, d2N, fixed, "KeyCoalescingFunction" -> List ] //RepeatedTiming
} // Column

The upshot is that it'd be nice to have this functionality built-in but at least these versions seem serviceable including the option to maintain some of JoinAcross's current semantics.
Definitions
<< GeneralUtilities` (* to gain a "where-with-all" *)
MyJoinAcrossMerge[datasets : {__Dataset}, across : {__String}, butAlsoMerge :{__String}, by_: Identity] :=
Module[{wrapLeftRight, peelWrapper, Collapse},
wrapLeftRight := Function[key, {Left@key, Right@key}];
peelWrapper := Function[wrapped, First@First@wrapped];
Collapse[{patt_, kf_}, mf_] := Function[assoc, Where[
rows = KeySelect[MatchQ@patt]@assoc,
keys = Keys@rows,
vals = mf /@ Thread@(Values@rows),
coll = (kf@(keys)) -> vals,
Insert[coll, Key[First@keys]]@assoc // KeyDrop[keys]
]];
(* Collapse[{*part*, *kf*} *mf*] collapses (row) keys matched by *patt* into a single key with a value equal to *mf* applied to the list of the values of the matched keys (The actual single key is defined by applying *kf* to the matched keys) *)
Where[
joinedT = JoinAcross[Sequence @@ datasets, across, "KeyCollisionFunction" -> wrapLeftRight]//Transpose//Normal,
Fold[(Collapse[{Left[#2] | Right[#2], peelWrapper}, by]@#1) &,
joinedT,
butAlsoMerge] // Dataset // Transpose
]];
(* Now converting to JoinAcross syntax *)
MyJoinAcross[d1_, d2_, fixed_, "KeyCoalescingFunction" -> f_] := Where[varying = Normal@Complement[Join @@ (Keys@First@# & /@ {d1, d2}), fixed],
MyJoinAcrossMerge[{d1, d2}, fixed, varying, f ]];
(* Kuba's answer and converted to JoinAcross syntax *)
KyJoinAcrossMerge[datasets : {__Dataset}, across : {__String}, butAlso : {__String}, by_: Identity] :=
GroupBy[Join @@ datasets,
Lookup[across] -> KeyTake[Join[across, butAlso]],
If[Length[#] == 1, <||>, <|#[[1]][[across]],
Merge[#[[;; , butAlso]], by]|>] &][Values@*DeleteCases[<||>]] //
Dataset
KyJoinAcross[d1_, d2_, fixed_, "KeyCoalescingFunction" -> f_] :=
Where[varying =
Normal@Complement[Join @@ (Keys@First@# & /@ {d1, d2}), fixed],
KyJoinAcrossMerge[{d1, d2}, fixed, varying, f ]];
(* alancalvitti's answer converted to JoinAcross syntax (List merging only) *)
CyJoinAcrossMerge[datasets : {__Dataset}, across : {__String},
butAlso : {__String}, List] :=
Dataset[datasets][All, Normal][All, GroupBy[Query[across]], All,
butAlso][
Merge[Merge[Identity]] /*
Select[Function[row, Length[row[First@butAlso]] == 2]] /*
KeyValueMap[Join]]
CyJoinAcross[d1_, d2_, fixed_, "KeyCoalescingFunction" -> List] :=
Where[varying =
Normal@Complement[Join @@ (Keys@First@# & /@ {d1, d2}), fixed],
CyJoinAcrossMerge[{d1, d2}, fixed, varying, List ]];
(* alancalvitti's converted but with arbitrary merging function *)
CyJoinAcrossMerge[datasets : {__Dataset}, across : {__String},
butAlso : {__String}, by_: Identity] :=
Dataset[datasets][All, Normal][All, GroupBy[Query[across]], All,
butAlso][
Merge[Merge[If[Length@# == 2, by@#, dummy] &]] /*
KeyValueMap[Join]][
Select[Function[row, Head[row[First@butAlso]] === by]]]
CyJoinAcross[d1_, d2_, fixed_, "KeyCoalescingFunction" -> f_] :=
Where[varying =
Normal@Complement[Join @@ (Keys@First@# & /@ {d1, d2}), fixed],
CyJoinAcrossMerge[{d1, d2}, fixed, varying, f ]];
KeyCollisionFunctionis strangely limited :-/ – Kuba Feb 24 '16 at 12:38