EPlat
EPlat (E-commerce Platform)
A single interface for interacting with E-commerce platform APIs.
Goals:
-
Make it easy to use and request API resources from different platforms.
-
Offer a universal alias interface for interacting with common natured attributes. e.g. Shopify's product.body_html and BigCommerce's product.description.
-
Have a simple, easy-to-understand project structure.
-
The e_plat alias interface mimics Shopify's API schema names and types.
Installation
Install the gem and add to the application's Gemfile by executing:
$ gem "e_plat"
To your gemfile and then run
$ bundle
Config
You can configure which platform APIs are used by updating the EPlat config:
EPlat.config.shopify_api_version = "2024-07"
EPlat.config.bigcommerce_api_version = "v3"
Testing
To run the test for the default versions of all supported platforms:
bundle exec rails test
To run with specific api versions, you can set globals inline
EPLAT_SHOPIFY_API_VERSION=2024_07 EPLAT_BIGCOMMERCE_API_VERSION=v3 rails test
To print out Shopify GraphQL queries/mutations
EPLAT_PRINT_GRAPHQL_REQUESTS=true rails test
Releasing a new version
https://dev.to/doctolib/release-a-new-gem-version-je0
Usage
- To make requests, first initialize a session like so:
EPlat::Session.new(
platform: :shopify,
store_url: "test-store.myshopify.com",
api_token: "123",
)
- You can then make requests to the platform via a singleton call on the Resource class:
EPlat::Product.find(id: 123)
- Which will return a platform resource instance that inherits from:
EPlat::Product
EPlat is designed to let you mostly use it's universal interface, but then easily interact with non supported platform attributes when needed.
tag = EPlat::ScriptTag.new(
display_scope: :online_store,
event: :onload,
src: "https://preproduct.io/mini-script.js"
)
tag.name = "mini script" if EPlat::Current.e_plat_session.bigcommerce?
tag.save
Under The Hood
-
Request/Resources follow this format: EPlat::ResourceName. Requests are called on the class, which then return an instance.
-
EPlat resources will return their platform's native attributes, alongside a universal alias interface for viewing/editing via the EPlat schema.
-
EPlat aliases can always be safely called, but aren't always successfully mapped to underlying platform attributes.
-
Whilst the EPlat alias interface and methods often mean you can treat resources the same regardless of platform, you can also ignore this interface and interact with the platform native attributes like regular active resource/record object.
-
The EPlat alias interface is just a set of dynamically created getter/setter/predicate methods with types that control the native attributes of the resource.
You can check if an attribute has been mapped by calling `resource.mapped? "attribute_name"`
EPlat Alias Interface
Shop
EPlat::Shop
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer | * | nil | nil |
name | string | * | name | nil |
email | string | * | admin_email | nil |
domain | string | * | domain | nil |
province | string | * | nil | nil |
country | string | * | country | nil |
address1 | string | * | nil | nil |
zip | string | * | nil | nil |
city | string | * | nil | nil |
source | string | * | nil | nil |
phone | string | * | nil | nil |
latitude | float | * | nil | nil |
longitude | float | * | nil | nil |
primary_locale | string | * | nil | nil |
address2 | string | * | nil | nil |
created_at | datetime | * | nil | nil |
updated_at | datetime | * | nil | nil |
country_code | string | * | country_code | nil |
country_name | string | * | nil | nil |
currency | string | * | currency | nil |
customer_email | string | * | nil | nil |
timezone | string | * | nil | nil |
iana_timezone | string | * | nil | nil |
shop_owner | string | * | nil | nil |
money_format | string | * | nil | nil |
money_with_currency_format | string | * | nil | nil |
weight_unit | string | * | weight_units | nil |
province_code | string | * | nil | nil |
taxes_included | boolean | * | nil | nil |
auto_configure_tax_inclusivity | boolean | * | nil | nil |
tax_shipping | boolean | * | nil | nil |
county_taxes | boolean | * | nil | nil |
plan_display_name | string | * | nil | nil |
plan_name | string | * | plan_name | nil |
has_discounts | boolean | * | nil | nil |
has_gift_cards | boolean | * | nil | nil |
myshopify_domain | string | * | control_panel_base_url | nil |
google_apps_domain | string | * | nil | nil |
google_apps_login_enabled | boolean | * | nil | nil |
money_in_emails_format | string | * | nil | nil |
money_with_currency_in_emails_format | string | * | nil | nil |
eligible_for_payments | boolean | * | nil | nil |
requires_extra_payments_agreement | boolean | * | nil | nil |
password_enabled | boolean | * | nil | nil |
has_storefront | boolean | * | nil | nil |
eligible_for_card_reader_giveaway | boolean | * | nil | nil |
finances | boolean | * | nil | nil |
primary_location_id | integer | * | nil | nil |
cookie_consent_level | string | * | nil | nil |
visitor_tracking_consent_preference | string | * | nil | nil |
checkout_api_supported | boolean | * | nil | nil |
multi_location_enabled | boolean | * | nil | nil |
setup_required | boolean | * | nil | nil |
pre_launch_enabled | boolean | * | nil | nil |
enabled_presentment_currencies | array | * | nil | nil |
transactional_sms_disabled | boolean | * | nil | nil |
marketing_sms_consent_enabled_at_checkout | boolean | * | nil | nil |
shop.myshopify_domain
shop.domain
Product
EPlat::Product
Alias | Type | Shopify | BigCommerce | Etc |
---|
body_html | string | * | description | nil |
created_at | datetime | * | date_created | nil |
handle | string | * | name | nil |
id | integer | * | id | nil |
images | array | * | * | nil |
options | array | * | * | nil |
product_type | string | * | type | nil |
published_at | datetime | * | nil | nil |
status | string | * | availability | nil |
tags | string | * | nil | nil |
template_suffix | string | * | nil | nil |
title | string | * | name | nil |
updated_at | datetime | * | date_modified | nil |
variants | array | * | * | nil |
vendor | string | * | nil | nil |
EPlat::Product::Variant
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer | * | id | nil |
title | string | * | option_values&.map(&:label)&.join(' ') | nil |
price | string | * | price | nil |
sku | string | * | sku | nil |
position | integer | * | nil | nil |
inventory_policy | string | * | nil | nil |
compare_at_price | string | * | retail_price | nil |
inventory_management | boolean | inventoryItem[tracked] | nil | nil |
created_at | datetime | * | nil | nil |
updated_at | datetime | * | nil | nil |
taxable | boolean | * | nil | nil |
barcode | string | * | nil | nil |
grams | integer | getter only: converted node.inventoryItem[measurement][weight][value] | nil | nil |
weight | float | getter only: node.inventoryItem[measurement][weight][value] | weight | |
weight_unit | string | getter only: node.inventoryItem[measurement][weight][unit] | nil | |
inventory_item_id | integer | getter only: node.inventoryItem[id] | nil | nil |
inventory_quantity | integer | getter only: node.inventoryQuantity | inventory_level | nil |
tax_code | string | * | nil | nil |
requires_shipping | boolean | InventoryItem[requiresShipping] | nil | nil |
helper methods:
image_src
=> String | nil
, platforms supported: Shopify, Bigcommerce
*Bigcommerce variant.title= setter is not supported
EPlat::Product::Option
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer | * | id | nil |
name | string | * | display_name | nil |
position | integer | * | sort_order | nil |
values | array | * | option_values[i][label] | nil |
EPlat::Product::Image
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer | * | id | nil |
position | integer | * | nil | nil |
alt | string | * | nil | nil |
width | integer | * | nil | nil |
height | integer | * | nil | nil |
src | string | * | url_standard | nil |
EPlat::Product::Variant::OptionValue
(not supported for Shopify API v2024_01)
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer | [selected_options][option_value][id] | id | nil |
name | string | [selected_options][name] | option_display_name | nil |
value | string | [selected_options][value] | label | nil |
Order
EPlat::Order
attribute_name | type | Shopify | BigCommerce | Etc |
---|
id | integer | * | id | nil |
app_id | integer | * | nil | nil |
billing_address_address1 | string | * | nil | nil |
billing_address_address2 | string | * | nil | nil |
billing_address_city | string | * | nil | nil |
billing_address_company | string | * | nil | nil |
billing_address_country | string | * | nil | nil |
billing_address_first_name | string | * | nil | nil |
billing_address_last_name | string | * | nil | nil |
billing_address_phone | string | * | nil | nil |
billing_address_province | string | * | nil | nil |
billing_address_zip | string | * | nil | nil |
billing_address_name | string | * | nil | nil |
billing_address_province_code | string | * | nil | nil |
billing_address_country_code | string | * | nil | nil |
billing_address_latitude | string | * | nil | nil |
billing_address_longitude | string | * | nil | nil |
browser_ip | string | * | nil | nil |
buyer_accepts_marketing | boolean | * | nil | nil |
cancel_reason | string | * | nil | nil |
cancelled_at | datetime | * | nil | nil |
cart_token | string | * | nil | nil |
checkout_token | string | * | nil | nil |
client_details | hash | * | nil | nil |
closed_at | datetime | * | nil | nil |
created_at | datetime | * | date_created | nil |
currency | string | * | currency_code | nil |
current_total_discounts | string | * | discount_amount | nil |
current_total_discounts_set | hash | * | nil | nil |
current_total_duties_set | hash | * | nil | nil |
current_total_price | string | * | nil | nil |
current_total_price_set | hash | * | nil | nil |
current_subtotal_price | string | * | nil | nil |
current_subtotal_price_set | hash | * | nil | nil |
current_total_tax | string | * | nil | nil |
current_total_tax_set | hash | * | nil | nil |
customer_locale | string | * | customer_locale | nil |
device_id | integer | * | nil | nil |
discount_codes | array | * | nil | nil |
email | string | * | billing_address[email] | nil |
estimated_taxes | boolean | * | nil | nil |
financial_status | string | * | status_id | nil |
fulfillment_status | string | * | nil | nil |
gateway | string | * | nil | nil |
landing_site | string | * | nil | nil |
landing_site_ref | string | * | nil | nil |
location_id | integer | * | nil | nil |
merchant_of_record_app_id | integer | * | nil | nil |
name | string | * | nil | nil |
note | string | * | staff_notes | nil |
note_attributes | array | * | nil | nil |
number | integer | * | nil | nil |
order_number | integer | * | id | nil |
order_status_url | string | * | nil | nil |
original_total_duties_set | hash | * | nil | nil |
payment_gateway_names | array | * | nil | nil |
phone | string | * | nil | nil |
presentment_currency | string | * | nil | nil |
processed_at | string | * | nil | nil |
processing_method | string | * | nil | nil |
reference | string | * | nil | nil |
referring_site | string | * | nil | nil |
source_identifier | string | * | nil | nil |
source_name | string | * | nil | nil |
source_url | string | * | nil | nil |
subtotal_price | string | * | subtotal_ex_tax | nil |
subtotal_price_set | hash | * | nil | nil |
tags | string | * | nil | nil |
tax_lines | array | * | nil | nil |
taxes_included | boolean | * | nil | nil |
test | boolean | * | nil | nil |
token | string | * | nil | nil |
total_discounts | string | * | nil | nil |
total_discounts_set | hash | * | nil | nil |
total_line_items_price | string | * | nil | nil |
total_line_items_price_set | hash | * | nil | nil |
total_outstanding | string | * | nil | nil |
total_price | string | * | total_inc_tax | nil |
total_price_set | hash | * | nil | nil |
total_shipping_price_set | hash | * | nil | nil |
total_tax | string | * | total_tax | nil |
total_tax_set | hash | * | nil | nil |
total_tip_received | string | * | nil | nil |
total_weight | integer | * | nil | nil |
updated_at | datetime | * | date_modified | nil |
billing_address | hash | * | billing_address | nil |
customer | hash | * | nil | nil |
discount_applications | array | * | nil | nil |
fulfillments | array | * | nil | nil |
line_items | array | * | nil | nil |
payment_details | hash | * | nil | nil |
payment_terms | hash | * | nil | nil |
refunds | array | * | nil | nil |
shipping_address | hash | * | nil | nil |
shipping_lines | array | * | nil | nil |
EPlat::Order::ShippingAddress
Alias | Type | Shopify | BigCommerce | Etc |
---|
address1 | string | * | street_1 | nil |
address2 | string | * | street_2 | nil |
city | string | * | city | nil |
company | string | * | company | nil |
country | string | * | country | nil |
first_name | string | * | first_name | nil |
last_name | string | * | last_name | nil |
phone | string | * | phone | nil |
province | string | * | state | nil |
zip | string | * | zip | nil |
country_code | string | * | country_iso2 | nil |
province_code | string | * | nil | nil |
latitude | float | * | nil | nil |
longitude | float | * | nil | nil |
EPlat::Order::BillingAddress
Alias | Type | Shopify | BigCommerce | Etc |
---|
address1 | string | * | street_1 | nil |
address2 | string | * | street_2 | nil |
city | string | * | city | nil |
company | string | * | company | nil |
country | string | * | country | nil |
first_name | string | * | first_name | nil |
last_name | string | * | last_name | nil |
phone | string | * | phone | nil |
province | string | * | state | nil |
zip | string | * | zip | nil |
country_code | string | * | country_iso2 | nil |
province_code | string | * | nil | nil |
latitude | float | * | nil | nil |
longitude | float | * | nil | nil |
EPlat::Order::LineItem
Attribute | Type | Shopify | BigCommerce | Etc |
---|
id | integer | * | order[consignments][0][shipping][0][line_items][#{i}][id] | nil |
admin_graphql_api_id | string | * | nil | nil |
fulfillable_quantity | integer | * | nil | nil |
fulfillment_service | string | * | nil | nil |
fulfillment_status | string | * | nil | nil |
gift_card | boolean | * | nil | nil |
grams | integer | * | nil | nil |
name | string | * | order[consignments][0][shipping][0][line_items][#{i}][name] | nil |
price | string | * | order[consignments][0][shipping][0][line_items][#{i}][price] | nil |
price_set | hash | * | nil | nil |
product_exists | boolean | * | nil | nil |
product_id | integer | * | order[consignments][0][shipping][0][line_items][#{i}][product_id] | nil |
properties | array | * | nil | nil |
quantity | integer | * | order[consignments][0][shipping][0][line_items][#{i}][quantity] | nil |
requires_shipping | boolean | * | nil | nil |
sku | string | * | order[consignments][0][shipping][0][line_items][#{i}][sku] | nil |
taxable | boolean | * | nil | nil |
title | string | * | nil | nil |
total_discount | string | * | nil | nil |
total_discount_set | hash | * | nil | nil |
variant_id | integer | * | order[consignments][0][shipping][0][line_items][#{i}][variant_id] | nil |
variant_inventory_management | string | * | nil | nil |
variant_title | string | * | nil | nil |
vendor | string | * | nil | nil |
tax_lines | array | * | nil | nil |
duties | array | * | nil | nil |
discount_allocations | array | * | nil | nil |
*line_item uses a virtual collection for BigCommerce, this allows us to present with the correct nesting position, whilst updating the native much deeper nested set below
Metafield
EPlat::Metafield
Alias | Type | Shopify | BigCommerce | Etc |
---|
created_at | datetime | * | date_created | nil |
description | string | * | description | nil |
id | integer | * | id | nil |
key | string | * | key | nil |
namespace | string | * | namespace | nil |
owner_id | integer | * | resource_id | nil |
owner_resource | string | * | resource_type | nil |
updated_at | datetime | * | date_modified | nil |
value | string | * | value | nil |
type | string | * | nil | nil |
*Note: Bigcommerce always assumes a type of String and a native attribute permission_set of "read_and_sf_access"
ScriptTag
EPlat::ScriptTag
Alias | Type | Shopify | BigCommerce | Etc |
---|
id | integer/string | id | uuid | |
src | string | src | src | |
created_at | datetime | created_at | date_created | |
updated_at | datetime | updated_at | date_modified | |
display_scope | string | display_scope | visibility | |
event | string | event | nil | |
cache | boolean | cache | nil | |
*display_scope available options: online_store
, order_status
, all
**Bigcommerce has some native attribute defaults set, which can be overwritten if needed:
location: "head"
, auto_uninstall: true
, kind: "src"
, load_method: "async"
, name: "app-script"
, enabled: true
Webhook
EPlat::Webhook
Alias | Type | Shopify | BigCommerce | Etc |
---|
address | string | address | destination | |
api_version | string | api_version | nil | |
created_at | datetime/Int | created_at | created_at | |
fields | array | fields | nil | |
format | string | format | nil | |
id | integer | id | id | |
metafield_namespaces | array | metafield_namespaces | nil | |
private_metafield_namespaces | array | private_metafield_namespaces | nil | |
topic | string | topic | scope | |
updated_at | datetime/Int | updated_at | updated_at | |
*EPlat doesn't make any attemps to alias the varias topic/scope values between platforms.
**Notice that BigCommerce uses updated_at and created_at for this resource.
Unfortunately it's in a Time Integer format. Haven't yet got dynamic conversion so just tread carefully.
API Coverage
Base / Config
Method | ShopifyAPI Gem equivalent | Shopify - tested | BigCom - tested |
---|
EPlat::Session.new() | ::Session.new | * | * |
EPlat::Session.new() | ::Base.activate_session | * | * |
EPlat::Session.new() | ::Session.setup() | * | * |
EPlat.config.values[:shopify_api_version] | ::Baseapi_version | * | |
EPlat.config.values[:bigcommerce_api_version] | ::Baseapi_version | | * |
Shop
Method | Shopify - tested | BigCom - tested | Etc |
---|
::Shop.current | * | * | |
shop.attribute | * | * | |
Product
Method | Shopify - tested | BigCom - tested | Etc |
---|
::Product.last | * | * | |
::Product.all | * | * | |
::Product.attribute | * | * | |
::Product.find(id) | * | * | |
::Product.where(attr: '') | * | * | |
::Product.find_by(attr: '') | * | * | |
::Product.create(attr:) | * | * | |
::Product.save | * | * | |
.save saves nested resources | only variants | | |
::Product.delete(id) | * | * | |
::Product.count | * | * | |
product.dup | * | * | |
product.variants | * | * | |
product.load_all_variants | * | * | |
product.find_variant(id) | * | * | |
product.images | * | * | |
products.next_page? | * | * | |
products.previous_page? | * | * | |
products.fetch_next_page | * | * | |
products.fetch_previous_page | * | * | |
products.previous_page_info | * | * | |
products.next_page_info | * | * | |
@product.load_all_variants
<%= link_to "previous products", "/", forward_page_info: products.previous_page_info %>
<%= link_to "Next products", "/", backward_page_info: products.next_page_info %>
EPlat::Product.find(:all, params: {
EPlat::Current.e_plat_session.pagination_param(:forward) => params['forward_page_info']
})
EPlat::Product.find(:all, params: {
EPlat::Current.e_plat_session.pagination_param(:backward) => params['backward_page_info']
})
Product::Variant
Method | Shopify - tested | BigCom - tested | Etc |
---|
product.variants | * | * | |
product.find_variant | * | * | |
variants.attribute | * | * | |
variant.attributes.delete | * | * | |
variant.save | * | * | |
- Nested resources currently need to be saved on their own instance. product.variants.first.save, as opposed to product.save
- Also bear in mind that for platforms like BigCommerce, product and variant IDs are only unique within that store's context
- so if storing these IDs locally, remember to query via the shop i.e. shop.listings.where(platform_product_id: 123)
Order
Method | Shopify - tested | BigCom - tested | Etc |
---|
::Order.last | * | * | |
::Order.all | * | * | |
::Order.attribute | * | * | |
::Order.find(id) | * | * | |
::Order.where(title: '') | * | * | |
::Order.find_by(title: '') | * | * | |
::Order.create(attr: '', ) | * | * | |
::Order.new(attr: '', ) | * | * | |
::Order.save | * | * | |
::Order.count | * | * | |
order.cancel | * | * | |
orders.next_page? | * | nil | |
orders.previous_page? | * | nil | |
orders.fetch_next_page | * | nil | |
orders.fetch_previous_page | * | nil | |
*included nested resources: can update non-resource nested hashes like shipping_address, but not proper resources like customer
Metafield
Method | Shopify - tested | BigCom - tested | Etc |
---|
product.metafields | * | * | |
order.metafields | * | * | |
product.find_metafield(id) | * | * | |
order.find_metafield(id) | * | * | |
order.find_metafield(id) | * | * | |
product.add_metafield(EPlat::Metafield.new {}) | * | * | |
order.add_metafield(EPlat::Metafield.new {}) | * | * | |
.attribute | * | * | |
.save | * | * | |
.destroy | * | * | |
::Metafield.create(attr: '') | * | * | |
::Metafield.new(attr: '') | * | * | |
ScriptTag
Method | Shopify - tested | BigCom - tested | Etc |
---|
::ScriptTag.new({}) | * | * | |
::ScriptTag.create({}) | * | * | |
::ScriptTag.last | * | * | |
::ScriptTag.all | * | * | |
::ScriptTag.find | * | * | |
.attribute | * | * | |
.save | * | * | |
.destroy | * | * | |
*note that Bigcommerce uses UUID instead of ID for script tags. This should mostly be handled by EPlat, apart from when passing the value to .find
Webhook
Method | Shopify - tested | BigCom - tested | Etc |
---|
::Webhook.new({}) | * | * | |
::Webhook.create({}) | * | * | |
::Webhook.last | * | * | |
::Webhook.all | * | * | |
::Webhook.find | * | * | |
.attribute | * | * | |
.save | * | * | |
.destroy | * | * | |
Shopify only
These areas of the API will just support Shopify.
RecurringApplicationCharge
Method | Shopify - tested |
---|
::RecurringApplicationCharge.current | * |
::RecurringApplicationCharge.new({}) | * |
::RecurringApplicationCharge.create({}) | * |
::RecurringApplicationCharge.last | * |
::RecurringApplicationCharge.all | * |
::RecurringApplicationCharge.find | * |
.attribute | * |
.save | * |
.cancel | * |
.destroy | * |
.usage_charges | * |
*The .current charge is the active charge for the app with the session's store. Only one charge can be active at any time.
RecurringApplicationCharge::UsageCharge
Method | Shopify - tested |
---|
::UsageCharge.last(params: {recurring_application_charge: r.id}) | * |
::UsageCharge.all(params: {recurring_application_charge: r.id}) | * |
::UsageCharge.find(id, params: {recurring_application_charge: r.id}) | * |
::UsageCharge.new({recurring_application_charge_id: r.id, ...}) | * |
::UsageCharge.create({recurring_application_charge_id: r.id, ...}) | * |
.save | * |
.attribute | * |
*Note no :update or :delete options supported.
** the recurring_application_charge_id has to be passed to any requests called on the class.
Outside of the scope of EPlat
-
ShopifyAPI::Asset is depricated.
(check_for_store_two_pont_o in main app code.)
-
ShopifyAPI::AccessScope doesn't have other platform API equivilents.
(although should be kept track of locally)
[!NOTE]
For all resources, we also have a EPlat::ShopifyWebhook::{resource} class. These use the old REST API mappings that the webhooks still use.
At PreProduct we interact with cached webhook hashes using these classes, e.g. EPlat::ShopifyWebhook::Product.new({...})
.
Meaning we can safely call getter methods and access the usual EPlat schema.
Request Syntax
EPlat::Session.new(
platform: :shopify,
store_url: "hi.myshopify.com",
api_token: 11223344
)
product = EPlat::Product.new(title: 't-shirt')
product.save
EPlat::Product.create(title: 't-shirt')
new_product = product.dup
EPlat::Product.delete(params[:id])
EPlat::Product.find(my_id).destroy
product = EPlat::Product.new(title: 't-shirt')
product.save
product.errors
product.formatted_id
product.full_response
product.headers
JSON.parse(product.full_response.body)
EPlat::Product.find(101)
EPlat::Product.all
EPlat::Product.last
EPlat::Product.where(title: "tshirt")
EPlat::Product.find_by(title: "tshirt")
EPlat::Product.find(:all, params: {title: "tshirt"})
EPlat::Product.find(99, from: :leader)
EPlat::Product.find(99, from: "/product/1/specific_url.json")
EPlat::Product.count
EPlat::Product::Variant.find(variant_id, params: {product: product.id})
EPlat::Product::Variant.collection_path(product: 5)
Attribute info
product.attributes
product.mapped_attributes.entries
product.native_keys
product.mapped_attributes.
product.mapping.aliases
product.mapped? "body_html"
product.changed_attributes
product.title = "oi"
product.changed_attributes
product.as_json
product.as_full_json
product.to_json
product.to_full_json
product.as_eplat_json
product.to_eplat_json