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
1 change: 1 addition & 0 deletions app/controllers/admin/shop_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def available_shop_item_types
"ShopItem::HCBGrant",
"ShopItem::HCBPreauthGrant",
"ShopItem::HQMailItem",
"ShopItem::InkthreadableItem",
"ShopItem::LetterMail",
"ShopItem::ThirdPartyPhysical",
"ShopItem::ThirdPartyDigital",
Expand Down
77 changes: 77 additions & 0 deletions app/jobs/shop/send_inkthreadable_order_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# frozen_string_literal: true

class Shop::SendInkthreadableOrderJob < ApplicationJob
queue_as :default

def perform(order_id)
order = ShopOrder.find(order_id)
shop_item = order.shop_item

unless shop_item.is_a?(ShopItem::InkthreadableItem)
Rails.logger.warn "[Inkthreadable] Order #{order_id} is not an Inkthreadable item, skipping"
return
end

address = order.frozen_address
if address.blank?
Rails.logger.error "[Inkthreadable] Order #{order_id} missing address"
return
end

payload = build_order_payload(order, shop_item, address)

response = InkthreadableService.create_order(payload)

inkthreadable_order_id = response.dig("order", "id")
if inkthreadable_order_id.present?
order.update!(external_ref: "INK-#{inkthreadable_order_id}")
Rails.logger.info "[Inkthreadable] Order #{order_id} submitted successfully as #{inkthreadable_order_id}"
else
Rails.logger.error "[Inkthreadable] Order #{order_id} response missing order.id: #{response.inspect}"
raise "Inkthreadable response missing order.id"
end
rescue Faraday::Error => e
Rails.logger.error "[Inkthreadable] Failed to send order #{order_id}: #{e.message}"
Rails.logger.error e.response&.dig(:body) if e.respond_to?(:response)
raise
end

private

def build_order_payload(order, shop_item, address)
payload = {
external_id: "FT-#{order.id}",
shipping_address: {
firstName: address["first_name"] || address["firstName"] || order.user.display_name.split.first,
lastName: address["last_name"] || address["lastName"] || order.user.display_name.split.last,
company: address["company"],
address1: address["address1"] || address["line1"],
address2: address["address2"] || address["line2"],
city: address["city"],
county: address["state"] || address["county"],
postcode: address["postcode"] || address["zip"] || address["postal_code"],
country: address["country"],
phone1: address["phone"] || address["phone1"]
}.compact,
shipping: {
shippingMethod: shop_item.shipping_method
},
items: build_order_items(order, shop_item)
}

payload[:brandName] = shop_item.brand_name if shop_item.brand_name.present?
payload[:comment] = "Flavortown order ##{order.id}" if Rails.env.production?

payload
end

def build_order_items(order, shop_item)
[
{
pn: shop_item.product_number,
quantity: order.quantity,
designs: shop_item.design_urls
}.compact
]
end
end
95 changes: 95 additions & 0 deletions app/models/shop_item/inkthreadable_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# == Schema Information
#
# Table name: shop_items
#
# id :bigint not null, primary key
# accessory_tag :string
# agh_contents :jsonb
# attached_shop_item_ids :bigint default([]), is an Array
# buyable_by_self :boolean default(TRUE)
# default_assigned_user_id_au :bigint
# default_assigned_user_id_ca :bigint
# default_assigned_user_id_eu :bigint
# default_assigned_user_id_in :bigint
# default_assigned_user_id_uk :bigint
# default_assigned_user_id_us :bigint
# default_assigned_user_id_xx :bigint
# description :string
# enabled :boolean
# enabled_au :boolean
# enabled_ca :boolean
# enabled_eu :boolean
# enabled_in :boolean
# enabled_uk :boolean
# enabled_us :boolean
# enabled_xx :boolean
# hacker_score :integer
# hcb_category_lock :string
# hcb_keyword_lock :string
# hcb_merchant_lock :string
# hcb_preauthorization_instructions :text
# internal_description :string
# limited :boolean
# long_description :text
# max_qty :integer
# name :string
# old_prices :integer default([]), is an Array
# one_per_person_ever :boolean
# payout_percentage :integer default(0)
# price_offset_au :decimal(, )
# price_offset_ca :decimal(, )
# price_offset_eu :decimal(, )
# price_offset_in :decimal(, )
# price_offset_uk :decimal(10, 2)
# price_offset_us :decimal(, )
# price_offset_xx :decimal(, )
# sale_percentage :integer
# show_in_carousel :boolean
# site_action :integer
# special :boolean
# stock :integer
# ticket_cost :decimal(, )
# type :string
# unlock_on :date
# usd_cost :decimal(, )
# created_at :datetime not null
# updated_at :datetime not null
# default_assigned_user_id :bigint
# user_id :bigint
#
# Indexes
#
# index_shop_items_on_default_assigned_user_id (default_assigned_user_id)
# index_shop_items_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (default_assigned_user_id => users.id) ON DELETE => nullify
# fk_rails_... (user_id => users.id)
#
class ShopItem::InkthreadableItem < ShopItem
def fulfill!(order)
Shop::SendInkthreadableOrderJob.perform_later(order.id)
order.queue_for_fulfillment!
end

def inkthreadable_config
agh_contents || {}
end

def product_number
inkthreadable_config["pn"]
end

def design_urls
inkthreadable_config["designs"] || {}
end

def shipping_method
inkthreadable_config["shipping_method"] || "regular"
end

def brand_name
inkthreadable_config["brand_name"]
end
end
60 changes: 60 additions & 0 deletions app/services/inkthreadable_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module InkthreadableService
BASE_URL = "https://www.inkthreadable.co.uk"

class << self
def _conn
@conn ||= Faraday.new(url: BASE_URL) do |faraday|
faraday.request :json
faraday.response :json
faraday.response :raise_error
end
end

def create_order(data)
body = data.to_json
signature = generate_signature(body)

_conn.post("/api/orders.php") do |req|
req.params["AppId"] = app_id
req.params["Signature"] = signature
req.body = body
end.body
end

def get_order(order_id)
query_string = "id=#{order_id}"
signature = generate_signature(query_string)

_conn.get("/api/order.php") do |req|
req.params["AppId"] = app_id
req.params["id"] = order_id
req.params["Signature"] = signature
end.body
end

def list_orders(params = {})
query_parts = params.map { |k, v| "#{k}=#{v}" }.sort.join("&")
signature = generate_signature(query_parts)

_conn.get("/api/orders.php") do |req|
req.params["AppId"] = app_id
params.each { |k, v| req.params[k.to_s] = v }
req.params["Signature"] = signature
end.body
end

private

def app_id
Rails.application.credentials.dig(:inkthreadable, :app_id)
end

def secret_key
Rails.application.credentials.dig(:inkthreadable, :secret_key)
end

def generate_signature(request_body)
Digest::SHA1.hexdigest("#{request_body}#{secret_key}")
end
end
end
Loading