Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/store/catalog/product.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Elementary.Store.Catalog.Product do

@enforce_keys [:id]

defstruct [:id, :name, :description, :category, :price_range, :thumbnail_url, :variants]
defstruct [:id, :name, :description, :category, :type, :price_range, :thumbnail_url, :variants]

@doc """
Converts some Printful API data to an `Elementary.Store.Catalog.Product`
Expand All @@ -26,6 +26,7 @@ defmodule Elementary.Store.Catalog.Product do
name: product.sync_product.name,
description: catalog.product.description,
category: Category.from_printful(catalog, product),
type: String.downcase(catalog.product.type_name),
price_range: [hd(price_range), List.last(price_range)],
thumbnail_url: product.sync_product.thumbnail_url
)
Expand Down
2 changes: 2 additions & 0 deletions lib/store/catalog/variant.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Elementary.Store.Catalog.Variant do

defstruct [
:id,
:product_id,
:catalog_variant_id,
:name,
:description,
Expand Down Expand Up @@ -35,6 +36,7 @@ defmodule Elementary.Store.Catalog.Variant do

struct(__MODULE__,
id: store.id,
product_id: store.sync_product_id,
catalog_variant_id: store.variant_id,
name: store.name,
description: catalog.product.description,
Expand Down
163 changes: 76 additions & 87 deletions lib/store/fulfillment/fulfillment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,102 @@ defmodule Elementary.Store.Fulfillment do

require Logger

@stripe_store_source "elementary/store#v1"
@stripe_payment_types ["card"]

def create_order(%Fulfillment.Order{} = order) do
tax_price =
%{recipient: printful_recipient(order)}
|> Printful.Tax.get()
|> calculate_taxes(order)

printful_response =
Printful.Order.create(%{
shipping: order.shipping_rate.id,
recipient: printful_recipient(order),
items: Enum.map(order.items, &printful_line_item/1),
retail_costs: %{
tax: tax_price,
shipping: order.shipping_rate.price
}
})
shipping_rates = Shipping.get_rates(order.address, order.items) |> IO.inspect(label: "shipping rates")

Stripe.Session.create(%{
cancel_url: Routes.checkout_url(Elementary.StoreWeb.Endpoint, :index),
success_url: Routes.result_url(Elementary.StoreWeb.Endpoint, :success),
payment_method_types: @stripe_payment_types,
allow_promotion_codes: true,
customer_email: order.email,
line_items:
Enum.map(order.items, &stripe_line_item/1) ++ stripe_extra_lines(printful_response),
line_items: Enum.map(order.items, &stripe_line_item/1),
locale: order.locale,
automatic_tax: %{
enabled: true
},
mode: "payment",
payment_intent_data: %{
description: "elementary Store",
metadata: %{
source: "elementary/store#v1",
printful_id: printful_response.id,
source: @stripe_store_source,
locale: Gettext.get_locale()
}
},
metadata: %{
source: "elementary/store#v1",
printful_id: printful_response.id,
locale: Gettext.get_locale()
source: @stripe_store_source,
locale: Gettext.get_locale(),
printful_cart: order.items |> Enum.into(%{}) |> Jason.encode!()
},
shipping_address_collection: %{
allowed_countries: ["US", "DE", "AU", "NZ", "GB"]
},
shipping_options: Enum.map(shipping_rates, &stripe_shipping_item/1)
} |> IO.inspect(label: "stripe object"))
rescue
e in Printful.ApiError -> {:error, e.message}
end

defp stripe_line_item({variant_id, quantity}) do
variant = Catalog.get_variant(variant_id)
product = Catalog.get_product(variant.product_id)

%{
price_data: %{
currency: "USD",
unit_amount: round(variant.price * 100),
tax_behavior: "exclusive",
product_data: %{
name: variant.name,
images: [variant.preview_url],
tax_code: stripe_tax_code(product)
}
},
quantity: quantity
}
end

# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
defp stripe_tax_code(%{type: type}) do
cond do
String.contains?(type, "t-shirt") -> "txcd_30011000"
String.contains?(type, "jacket") -> "txcd_30011000"
String.contains?(type, "sweatshirt") -> "txcd_30011000"
String.contains?(type, "hoodie") -> "txcd_30011000"
String.contains?(type, "laptop sleeve") -> "txcd_30060001"
true -> "txcd_99999999"
end
end

defp stripe_shipping_item(%Elementary.Store.Shipping.Rate{} = rate) do
%{
shipping_rate_data: %{
display_name: rate.name,
type: "fixed_amount",
fixed_amount: %{
amount: round(rate.price * 100),
currency: "USD"
},
metadata: %{
printful_id: rate.id
},
tax_behavior: "inclusive"
}
})
}
end

def fulfill_order(%Stripe.Session{metadata: %{"source" => @stripe_store_source}} = stripe_charge) do
IO.inspect(stripe_charge, label: "charge")

# printful_id
# |> Printful.Order.get()
# |> Email.order_created()
# |> Mailer.deliver_later()

# fulfill_order(printful_id)
end

defp printful_recipient(order) do
Expand All @@ -69,25 +122,6 @@ defmodule Elementary.Store.Fulfillment do
}
end

defp calculate_taxes(%{rate: tax_rate} = tax, order) do
item_total =
order.items
|> Enum.map(fn {variant_id, quantity} -> {Catalog.get_variant(variant_id), quantity} end)
|> Enum.map(fn {variant, quantity} -> variant.price * quantity end)
|> Enum.reduce(0, fn a, b -> a + b end)

taxable_amount =
if tax.shipping_taxable do
item_total + order.shipping_rate.price
else
item_total
end

Float.ceil(taxable_amount * tax_rate, 2)
end

defp calculate_taxes(_, _order), do: 0

defp printful_line_item({variant_id, quantity}) do
variant = Catalog.get_variant(variant_id)

Expand All @@ -99,49 +133,4 @@ defmodule Elementary.Store.Fulfillment do
name: variant.name
}
end

defp stripe_line_item({variant_id, quantity}) do
variant = Catalog.get_variant(variant_id)

%{
amount: round(variant.price * 100),
currency: "USD",
name: variant.name,
quantity: quantity,
images: [variant.preview_url]
}
end

defp stripe_extra_lines(printful_response) do
printful_response.retail_costs
|> Map.take([:shipping, :tax, :vat])
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|> Enum.map(fn {name, value} ->
{amount, _} = Float.parse(value)

%{
amount: round(amount * 100),
currency: "USD",
name: String.capitalize(to_string(name)),
quantity: 1
}
end)
end

def fulfill_order(%Stripe.Session{livemode: livemode, metadata: %{"printful_id" => printful_id}}) do
printful_id
|> Printful.Order.get()
|> Email.order_created()
|> Mailer.deliver_later()

if livemode do
fulfill_order(printful_id)
else
Logger.info("Not confirming order #{printful_id} due to stripe testmode payment")
end
end

def fulfill_order(printful_id) do
Printful.Order.confirm(printful_id)
end
end
8 changes: 3 additions & 5 deletions lib/store/fulfillment/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ defmodule Elementary.Store.Fulfillment.Order do
:email,
:phone_number,
:items,
:locale,
:shipping_rate
:locale
]

def create(cart, address, shipping_rate) do
def create(cart, address) do
%__MODULE__{
address: Map.drop(address, [:email, :phone_number]),
email: address.email,
phone_number: address.phone_number,
items: cart,
locale: Gettext.get_locale(),
shipping_rate: shipping_rate
locale: Gettext.get_locale()
}
end
end
50 changes: 6 additions & 44 deletions lib/store_web/live/checkout_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ defmodule Elementary.StoreWeb.CheckoutLive do
|> assign(:countries, Shipping.get_countries())
|> assign(:states, Shipping.get_states(grab_address(address).country))
|> assign(:address, grab_address(address))
|> assign(:shipping_rates, [])
|> assign(:shipping_rate, nil)

{:ok, new_socket}
end
Expand All @@ -45,22 +43,10 @@ defmodule Elementary.StoreWeb.CheckoutLive do
|> assign(:error, nil)
|> assign(:states, Shipping.get_states(updated_address.country))
|> assign(:address, updated_address)
|> assign(:shipping_rates, [])
|> assign(:shipping_rate, nil)

{:noreply, new_socket}
end

@impl true
def handle_event("change", %{"shipping" => %{"id" => shipping_rate_id}}, socket) do
shipping_rate =
Enum.find(socket.assigns.shipping_rates, fn rate ->
rate.id == shipping_rate_id
end)

{:noreply, assign(socket, :shipping_rate, shipping_rate)}
end

@impl true
def handle_event("change", _params, socket) do
{:noreply, socket}
Expand All @@ -70,44 +56,26 @@ defmodule Elementary.StoreWeb.CheckoutLive do
def handle_event("submit", %{"address" => address}, socket) do
updated_address = grab_address(address)

shipping_rates = Shipping.get_rates(updated_address, socket.assigns.cart)

new_socket =
socket
|> assign(:error, nil)
|> assign(:address, updated_address)
|> assign(:shipping_rates, shipping_rates)
|> assign(:shipping_rate, hd(shipping_rates))

{:noreply, new_socket}
rescue
e in Printful.ApiError -> {:noreply, assign(socket, :error, e.message)}
end

@impl true
def handle_event("submit", %{"shipping" => %{"id" => shipping_rate_id}}, socket) do
shipping_rate =
Enum.find(socket.assigns.shipping_rates, fn rate ->
rate.id == shipping_rate_id
end)

stripe_res =
socket.assigns.cart
|> Fulfillment.Order.create(socket.assigns.address, shipping_rate)
|> Fulfillment.Order.create(updated_address)
|> Fulfillment.create_order()

case stripe_res do
{:ok, stripe_session} ->
new_socket =
socket
|> assign(:error, nil)
|> assign(:shipping_rate, shipping_rate)
|> assign(:address, updated_address)
|> push_event("sessionRedirect", %{session_id: stripe_session.id})

{:noreply, new_socket}

{:error, %{message: message}} ->
{:noreply, assign(socket, :error, message)}

{:error, message} ->
{:noreply, assign(socket, :error, message)}
end
end

Expand All @@ -118,13 +86,7 @@ defmodule Elementary.StoreWeb.CheckoutLive do

@impl true
def handle_info({:cart_update, cart}, socket) do
new_socket =
socket
|> assign(:cart, cart)
|> assign(:shipping_rates, [])
|> assign(:shipping_rate, nil)

{:noreply, new_socket}
{:noreply, assign(socket, :cart, cart)}
end

@impl true
Expand Down
11 changes: 4 additions & 7 deletions lib/store_web/templates/checkout/address.html.leex
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

<%= form_for :address, Routes.address_path(Endpoint, :update), [
id: "address-form",
method: :patch,
phx_change: :change,
phx_submit: :submit,
phx_hook: "SetSession",
phx_hook: "Stripe",
class: "address-checkout"
], fn f -> %>
<%= label f, :name do %>
Expand Down Expand Up @@ -110,9 +109,7 @@
%>
</label>

<%= if length(@shipping_rates) == 0 do %>
<%= submit dgettext("checkout", "Get Shipping Rates"),
phx_disable_with: dgettext("checkout", "Fetching Shipping Rates")
%>
<% end %>
<%= submit dgettext("checkout", "Check Out"),
phx_disable_with: dgettext("checkout", "Checking Out")
%>
<% end %>
4 changes: 0 additions & 4 deletions lib/store_web/templates/checkout/index.html.leex
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@

<%= render Elementary.StoreWeb.CheckoutView, "address.html", assigns %>

<%= if length(@shipping_rates) > 0 do %>
<%= render Elementary.StoreWeb.CheckoutView, "shipping.html", assigns %>
<% end %>

<%= if @error != nil do %>
<div class="alert alert--error alert--bubble">
<%= @error %>
Expand Down
Loading