Hello, I’m fairly new to developing Shopify applications, and I’m still trying to figure stuff out and how to do things the right way.
I have created a “Shipping Discount Function” through the CLI, I am then trying to use a custom meta field like so:
run.graphql:
query RunInput {
cart {
deliveryGroups {
id,
deliveryOptions {
cost {
amount
currencyCode
}
}
}
cost {
subtotalAmount {
amount
currencyCode
}
totalAmount {
amount
currencyCode
}
}
}
discountNode {
metafield(namespace: "checkout_champ_free_shipping", key: "settings") {
jsonValue
value
}
}
}
And in my run.ts file I’m trying to get it like so:
interface ShippingDiscountSettings {
targetSetting: number;
discountValue: number;
discountType: "fixed" | "percent" | "free";
}
export function run(input: RunInput): FunctionRunResult {
const settingsArray: ShippingDiscountSettings[] = JSON.parse(
input.discountNode?.metafield?.jsonValue || input.discountNode?.metafield?.value || "[]"
);
// Get the first row of settings, or fallback to an empty object if none found
const { targetSetting, discountValue, discountType }: ShippingDiscountSettings = settingsArray[0] || {};
/* if (parseFloat(input.cart.cost.subtotalAmount.amount) < Number(targetSetting)){
return EMPTY_DISCOUNT;
}*/
let value: Value;
switch (discountType) {
case 'free':
value = {percentage: {value: '100'}};
break;
case 'fixed':
value = {fixedAmount: {amount: `${discountValue}`}};
break;
case 'percent':
value = {percentage: {value: `${Number(discountValue) * 100}`}};
break;
}
// Apply the discount to all delivery groups
const targets: Target[] = input.cart.deliveryGroups.map((group) => ({
deliveryGroup: { id: group.id },
}));
return {
discounts: [{
value: value || {fixedAmount: {amount: `${discountValue || '5'}`}},
targets: targets
}],
};
}
But nothing seem to work, the discount amount is always ‘5’, I have hard coded the namespace as “checkout_champ_free_shipping” and the key as “settings”, I haven’t used a namespace like “$app:discount-shipping” because I haven’t figured out how to load that namespace in my .liquid files.
In my admin interface have built a .tsx file that uses a loader function to retrieve the metafields (which works) and an action function that saves the metafields (which works)
Here is the loader and its helper function:
export const loader: LoaderFunction = async ({request}) => {
const {admin} = await authenticate.admin(request);
const metaFields = await getShopMetafields(admin, "checkout_champ_free_shipping", 10);
let settings = {};
if (metaFields && metaFields[0]) {
settings = metaFields[0];
}
return {
settings: settings,
}
};
export async function getShopMetafields(
admin: AdminApiContext<RestResources>,
namespace: string,
first: number = 10
) {
return await admin.graphql(`#graphql
query {
shop {
metafields(namespace: "${namespace}", first: ${first}) {
edges {
node {
id
namespace
key
value
type
}
}
}
}
}`
).then((e: any) => e.json())
.then((e: any) => e.data.shop.metafields.edges.map((edge: any) => edge.node));
}
Here is the action function and its helper:
export async function action({request}: {request: Request}) {
const formData = await request.formData();
const {admin} = await authenticate.admin(request);
const shop = await getShop(admin);
const responseMetafieldSet = await setMetaField(
admin,
"checkout_champ_free_shipping",
"settings",
"json",
JSON.stringify({
targetSetting: formData.get("targetSetting"),
discountValue: Number(formData.get("discountValue")),
discountType: formData.get("discountType"),
textAlignment: formData.get("textAlignment"),
completedBackground: formData.get("completedBackground"),
missingBackground: formData.get("missingBackground"),
}),
shop.id
);
export async function setMetaField(
admin: AdminApiContext<RestResources>,
namespace: string,
key: string,
type: string,
value: any,
ownerId: any,
): Promise<{
metafield?: {
id: string,
namespace: string,
key: string,
value: string,
},
userErrors?: {
field: string[],
message: string,
code: string,
}[]
}> {
// Send the mutation to create or update the metafield
const response = await admin.graphql(
`
mutation SetMetafields($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields {
id
namespace
key
value
}
userErrors {
field
message
code
}
}
}
`,
{
variables: {
metafields: [
{
namespace: namespace,
key: key,
type: type,
value: value,
ownerId: ownerId
}
]
}
}
);
const jsonResponse = await response.json();
const result = jsonResponse.data.metafieldsSet;
if (result.userErrors && result.userErrors.length > 0) {
return { userErrors: result.userErrors };
}
return { metafield: result.metafields[0] };
}
So basically the above loader/action function can retrieve and save the meta field with the metafields and setMetafields graphql function.
I am able to read the metafield in my .liquid file to build up an interface like so:
{% assign free_shipping_settings_json = shop.metafields.checkout_champ_free_shipping.settings %}
But I am not able to load the metafield in my run.ts/graphql.ts in my actual Shopify Shipping Extension
Can someone guide me to the right path? i would appreciate it a lot.
Also, it would be awesome if someone could explain to me why I can’t use an app owned namespace like: $app:shipping-discount as the namespace when using .liquid files.