You have the order backwards I think. The guard matches on the ‘match’ step, so charge(...) gets called, it tries to match it to {:stripe_ok}, if that succeeds then it tries the guard of has_payment, and that fails so it bails out.
You probably want it either put it in an if expression, or put it on the line above with {:ok, order}.
From your original code I assumed that the with was supposed to fail when has_payment != true (while Repo.insert was still invoked beforehand) - which apparently wasn’t your intention.
Ah yea, sorry for the confusion. My goal (which hopefully is clearer now) was to:
Save an order
Charge a payment, when one is passed
If there is a payment, and it fails, I want the order to rollback.
If there is no payment, then I want it to skip the charge() method and just save the order.
I would use Ecto.Multi to handle this. You can control how each event is handled and whether it rolls back the transaction or proceeds. For example, below, if the payment fails, the function returns {:error, _} which rolls everything back. But if there is no payment, it still returns {:ok, _} and will skip the payment and save the order.
Checkout this article for a good application of this.
def place_order(changeset, has_payment, payment_method) do
Multi.new()
|> Multi.run(:save_order, &insert_order(&1, changeset))
|> Multi.run(:check_payment, &check_for_payment(&1, has_payment))
|> Multi.run(:charge, &charge_with_stripe(&1, payment_method))
|> Repo.transaction()
end
defp insert_order(_multi, changeset) do
Repo.insert(changeset)
end
defp check_for_payment(_multi, true), do: {:ok, true}
defp check_for_payment(_multi, false), do: {:ok, false}
defp charge_with_stripe(%{check_for_payment: false}, _payment_method), do: {:ok, :skipped}
defp charge_with_stripe(%{save_order: order}, payment_method) do
case charge(order, payment_method) do
:stripe_ok ->
{:ok, "Payment processed"}
_ ->
{:error, "Payment failed"
end
end