#Overwriting Cart Service `update()` method

1 messages · Page 1 of 1 (latest)

placid solar
#

I am trying to add 2 custom fields to the Cart service. I had a look at the Cart update() method and it seems that it needs to be adjusted to add custom fields. I overwrote the method and added following two clauses:

....
     if ("internal_order_id" in data) {
          cart.internal_order_id = data.internal_order_id as string;
        }

        if ("is_partial_delivery" in data) {
          cart.is_partial_delivery = data.is_partial_delivery as boolean;
        }

        console.log("FULL CART: ", cart);
        const updatedCart = await cartRepo.save(cart);
....

I adjusted CartUpdateProps and StorePostCartsCartReq to allow me to post both fields (repo etc. is also extended and migrations ran).

When I log the cart right before the update I can see my new properties are there and everything seems in order, I can also see that the .save() method is called successfully.

When checking the DB after I do not see the fields though - they are just null. I have been trying for days now, what am I missing here? Thanks!

placid solar
#

Has anyone extended the Cart entity and would be willing to tell me what I might be missing? Am I not correctly extending all validators / methods? I went through the Medusa code line by line but cant seem to see the point I am missing, Thanks 🙏

burnt halo
#

are you sure you are using the transaction when getting the repository? also, maybe you should put those values in the metadata wdyt?

placid solar
#

Mhmm thanks but we do not want to use metadata for this I think... We want to do it "properly" as actual fields

#

are you sure you are using the transaction when getting the repository?
can you explain what you mean? I am using the full endpoint of the update() method. I just copied it over

#

import { CartService, StorePostCartsCartReq } from "@medusajs/medusa";
import { EntityManager } from "typeorm";

export default async (req, res) => {
const { id } = req.params;
const validated = req.validatedBody as StorePostCartsCartReq;

const cartService: CartService = req.scope.resolve("cartService");
const manager: EntityManager = req.scope.resolve("manager");

if (req.user?.customer_id) {
validated.customer_id = req.user.customer_id;
}

await manager.transaction(async (transactionManager) => {
await cartService.withTransaction(transactionManager).update(id, validated);
});
};

This is the endpoint I created for testing purposes.

#

And this is my custom implementation of the update() method:

class CartService extends MedusaCartService {
` async update(cartId: string, data: CartUpdateProps): Promise<Cart> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const cartRepo = transactionManager.withRepository(
this.cartRepository_
);
const relations = [
"items",
"items.variant",
"items.variant.product",
"shipping_methods",
"shipping_address",
"billing_address",
"gift_cards",
"customer",
"region",
"payment_sessions",
"region.countries",
"discounts",
"discounts.rule",
];

`

#

also I want to add a "salutation" to the address entity (e.g. Mr or Ms or Company) which I do not want to have in the metadata I believe. Unless you say its an absolute pain to add custom fields, I would rather have them as proper fields than "metadata"

burnt halo
#

You can add custom fields, but if you completely override the update method with custom implementation then it should work. can you share the smallest reproducible repo so that I can have a proper look to the code please

#

I can look as soon as I can

placid solar
#

src/migration/1689428677453-CartOrderOptionals.ts

import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";

export class CartOrderOptionals1689428677453 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.addColumns("cart", [
      new TableColumn({
        name: "internal_order_id",
        type: "varchar",
        isNullable: true,
      }),
      new TableColumn({
        name: "is_partial_delivery",
        type: "boolean",
        isNullable: false,
        default: true,
      }),
    ]);
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropColumns("customer", [
      "internal_order_id",
      "is_partial_delivery",
    ]);
  }
}

src/models/cart.ts

import { Entity } from "typeorm";
import {
  // alias the core entity to not cause a naming conflict
  Cart as MedusaCart,
} from "@medusajs/medusa";
import { IsOptional } from "class-validator";

@Entity()
export class Cart extends MedusaCart {
  @IsOptional()
  internal_order_id: string;

  @IsOptional()
  is_partial_delivery: boolean;
}
burnt halo
#

are the filenames the real ones?

placid solar
#

no they are not

#

sorry tried to clone the repo

#

its rather complicated

#

should I adjust the names with src/ prefix?

burnt halo
#

yes please, it would be easier, but just a subset so I don't have to see everything ahah

#

you can put the path, for example src/models/cart.ts

placid solar
#

see above

#

src/services/cart.ts

import { isDefined } from "medusa-core-utils";
import { EntityManager } from "typeorm";

import { Cart, DiscountRuleType } from "@medusajs/medusa";

import { CartUpdateProps } from "@medusajs/medusa/dist/types/cart";
import { setMetadata } from "@medusajs/medusa";
import { CartService as MedusaCartService } from "@medusajs/medusa";

class CartService extends MedusaCartService {
  async update(cartId: string, data: CartUpdateProps): Promise<Cart> {
    return await this.atomicPhase_(

....

and then it continues until:


        if ("completed_at" in data) {
          cart.completed_at = data.completed_at!;
        }

        if ("payment_authorized_at" in data) {
          cart.payment_authorized_at = data.payment_authorized_at!;
        }
        if ("internal_order_id" in data) {
          cart.internal_order_id = data.internal_order_id as string;
        }

        if ("is_partial_delivery" in data) {
          cart.is_partial_delivery = data.is_partial_delivery as boolean;
        }

        console.log("FULL DATA: ", data);
        const updatedCart = await cartRepo.save(cart);
        console.log("FULL CART: ", updatedCart);

        if (
          (data.email && data.email !== originalCartCustomer.email) ||
          (data.customer_id && data.customer_id !== originalCartCustomer.id)
        ) {
          await this.eventBus_
            .withTransaction(transactionManager)
            .emit(CartService.Events.CUSTOMER_UPDATED, updatedCart.id);
        }

        await this.eventBus_
          .withTransaction(transactionManager)
          .emit(CartService.Events.UPDATED, updatedCart);

        return updatedCart;
      }
    );
  }
}

export default CartService;
#

src/validators/cart.ts


export class StorePostCartsCartReq extends MedusaStorePostCartsCartReq {
  @IsString()
  @IsOptional()
  internal_order_id: string;

  @IsBoolean()
  @IsOptional()
  is_partial_delivery: boolean;
}

export class StorePostCartReq extends MedusaStorePostCartReq {
  @IsString()
  @IsOptional()
  internal_order_id: string;

  @IsBoolean()
  @IsOptional()
  is_partial_delivery: boolean;
}

registerOverriddenValidators(StorePostCartsCartReq);
registerOverriddenValidators(StorePostCartReq);
#

Adrien as always 🙏 HUGE thanks!

burnt halo
#

and can you share what are the output of data and updatedcart that you are logging please

placid solar
#
 StorePostCartsCartReq {
  internal_order_id: '12345',
  is_partial_delivery: false
}


FULL CART:  {
  object: 'cart',
  id: 'cart_01H5S7XFY732T3QQYYZ08MB3JE',
  created_at: 2023-07-20T08:50:56.576Z,
  updated_at: 2023-07-23T12:16:06.568Z,
  deleted_at: null,
  email: '[email protected]',
  billing_address_id: 'addr_01H611R6Y4YSF2VEPKKVYR1VAX',
  shipping_address_id: null,
  region_id: 'reg_01H4QR463KN76CCY8F1WHD8JRT',
  customer_id: 'cus_01H611N8SCQ9QGKNP88TPEKJ9V',
  payment_id: null,
  type: 'default',
  completed_at: null,
  payment_authorized_at: null,
  idempotency_key: null,
  metadata: null,
  sales_channel_id: 'sc_01H4QR3C40S6FZ781B3EZSPRWG',
  afterLoad: [Function (anonymous)],
  beforeInsert: [Function (anonymous)],
  items: [],
  shipping_methods: [],
  shipping_address: null,
  billing_address: Address {
    id: 'addr_01H611R6Y4YSF2VEPKKVYR1VAX',
    created_at: 2023-07-23T09:37:07.492Z,
    updated_at: 2023-07-23T09:37:07.492Z,
    deleted_at: null,
    customer_id: null,
    company: null,
    first_name: null,
    last_name: null,
    address_1: '123 test.com',
    address_2: null,
    city: null,
    country_code: null,
    province: null,
    postal_code: null,
    phone: null,
    metadata: null
  },

  customer: Customer {
    id: 'cus_01H611N8SCQ9QGKNP88TPEKJ9V',
    created_at: 2023-07-23T09:35:31.083Z,
    updated_at: 2023-07-23T09:35:31.083Z,
    deleted_at: null,
    email: '[email protected]',
    first_name: null,
    last_name: null,
    billing_address_id: null,
    phone: null,
    has_account: false,
    metadata: null,
    salutation: null,
    is_email_verified: false
  },
  payment_sessions: [],
  payment_session: undefined,
  discounts: [],
  internal_order_id: '12345',
  is_partial_delivery: false
}

(I removed the region {} object for space reasons)

#

you see that it is attached to the cart at the end of the object

burnt halo
#

yes, that is before or after save?

placid solar
#

Before

burnt halo
#

and then what do you get after?

#

oh, just spotted that your model is wrong, you are using the class-validators decorator to define you columns in the model

placid solar
#

import { IsOptional } from "class-validator"; you mean this?

burnt halo
#

you should have used @Column instead of @Isoptional

placid solar
#

let me check this

burnt halo
#

let me know

placid solar
#

omg its working...

#

@burnt halo this is a thank you tag, hope its ok 🙂

placid solar
#

Adrien do you think its generally smart to overwrite such a core method (cart.update()) especially with future updates etc.? I would have to update it manually every time there is an update to this method. On the other hand: We do not want essential attributes to live in metadata. It feels hacky and not proper somehow...