chargebee / chargebee-typescript

Typescript library for the Chargebee API.
https://apidocs.chargebee.com/docs/api?lang=typescript
MIT License
22 stars 16 forks source link

Async/Await support #2

Closed LucianoGanga closed 3 years ago

LucianoGanga commented 4 years ago

Hello!

I was wondering if you're thinking of adding async/await support to this client.

It will make the code much more readable and easier. For example, it will be nice to change this code:

chargebee.portal_session
    .create({
      redirect_url: 'MY_URL',
      customer: {
        id: 'customer-id'
      }
    })
    .request(function (error: any, result: any) {
      if (error) {
        //handle error
        console.log(error)
      } else {
        const portalSession = result.portal_session
        return portalSession
      }
    })

To something like this:

const portalSession = await chargebee.portal_session.create({
    redirect_url: 'MY_URL',
    customer: {
      id: 'customer-id'
    }
  }).request()

But "request" forces a callback call with 2 parameters inside.

Thanks! Luciano

L-U-C-K-Y commented 4 years ago

Async await would be MUCH cleaner, thanks for this suggestion!

isaac-scarrott commented 4 years ago

I would also love for this package to use async await. Would make the writing the code so much cleaner. I'm having to write a wrapper around this package to use promises

isaac-scarrott commented 4 years ago

@LucianoGanga @L-U-C-K-Y. After looking at the typescript declaration files and the code it turns out it does return a promise you just have to pass any value (even undefined) as a parameter to the request function, so maybe this could be an optional parameter?

const portalSession = await chargebee.portal_session.create({
    redirect_url: 'MY_URL',
    customer: {
      id: 'customer-id'
    }
  }).request(undefined)
L-U-C-K-Y commented 3 years ago

@LucianoGanga @L-U-C-K-Y. After looking at the typescript declaration files and the code it turns out it does return a promise you just have to pass any value (even undefined) as a parameter to the request function, so maybe this could be an optional parameter?

const portalSession = await chargebee.portal_session.create({
    redirect_url: 'MY_URL',
    customer: {
      id: 'customer-id'
    }
  }).request(undefined)

Thanks @isaac-scarrott your workaround works for me.

@cb-navaneedhan how about making the .request optional? Combined with #7 this would make the experience complete for me.

Edit:

I was facing issues when with async/await when the return is an array. In general, it seems that the response from the typescript library is returning an empty object (or array of empty objects), when console logging it. I also inspected it with the debugger, but it is empty.

When getting a list of something (for example payment sources) and there should be 1 entry, the return object just as a length of 1, but has empty contents.

Weird enough, when just accessing it, I am able to get the data.

For getting the payment sources I have used the following code:

  /**
   * Static function: Get payment sources list of a customer
   * @param params
   */
  static async getChargebeeCustomerPaymentSources(
    params: _payment_source.payment_source_list_params,
  ): Promise<PaymentSource[]> {
    const paymentSources: PaymentSource[] = [];

    // get payment sources list from a customer
    const paymentSourcesResponse = await chargebee.payment_source.list(params).request(undefined);

    // if there are payment sources push to array
    for (let i = 0; i < paymentSourcesResponse.list.length; i++) {
      const payment_source: PaymentSource = paymentSourcesResponse.list[i].payment_source;
      // if the payment source exists, push the payment source
      if (payment_source) {
        paymentSources.push(payment_source);
      }
    }

    logger.info(
      `getChargebeeCustomerPaymentSources - paymentSources array: ${JSON.stringify(
        paymentSources,
        null,
        2,
      )}`,
    );

    return paymentSources;
  }
smashah commented 3 years ago

@L-U-C-K-Y I'm also getting an empty response Result {} when creating a customer.

L-U-C-K-Y commented 3 years ago

@L-U-C-K-Y I'm also getting an empty response Result {} when creating a customer.

You have to access the object in order to get the values out of it (not sure why).

This is the code I use for creating a subscription. Note that I explicitly access the result object to get the values out of it. You should be able to do the same thing with customer.

export interface ChargebeeCreateSubscriptionResponse {
  subscription?: Subscription;
  customer?: Customer;
  card?: Card;
  invoice?: Invoice;
  unbilled_charges?: UnbilledCharge;
}
static async createChargebeeSubscription(params: _subscription.create_params): Promise<ChargebeeCreateSubscriptionResponse> {
    // create subscription
    const createdSubscription: ChargebeeCreateSubscriptionResponse = await chargebee.subscription
      .create(params)
      .request(undefined);

    // log response
    logger.info(`createSubscription - subscription: ${JSON.stringify(createdSubscription.subscription,null,2)}`);
    logger.info(`createSubscription - customer: ${JSON.stringify(createdSubscription.customer, null, 2)}`);
    logger.info(`createSubscription - card: ${JSON.stringify(createdSubscription.card, null, 2)}`);
    logger.info(`createSubscription - invoice: ${JSON.stringify(createdSubscription.invoice, null, 2)}`);
    logger.info(`createSubscription - unbilled_charges: ${JSON.stringify(createdSubscription.unbilled_charges,null,2)}`);

    // explicitly access and return the response from the chargebee API
    return {
      subscription: createdSubscription.subscription,
      customer: createdSubscription.customer,
      card: createdSubscription.card,
      invoice: createdSubscription.invoice,
      unbilled_charges: createdSubscription.unbilled_charges,
    };
  }
smashah commented 3 years ago

If anyone else is getting an empty Result {} using the proposed method (by @isaac-scarrott ) you can alternatively try this:

return await new Promise((resolve, reject) => chargebee.customer.create({
email,
id
}).request((error: any, result: any) => {
if (error) {
    reject(error)
} else {
    resolve(JSON.parse(JSON.stringify(`${result}`)))
}
})));
smashah commented 3 years ago

thanks @L-U-C-K-Y

isaac-scarrott commented 3 years ago

@smashah The empty result is due to it return some sort of JSON that cannot be directly logged. Try the .toString() method to see the structure of the JSON and then you can access it's keys in a normal way.

This is also an issue that needs to be solved. This could be due to using the q library and not native Javascript Promises. If this is the case native promises should be used.

smashah commented 3 years ago

Yes, a Request {} object is entirely unexpected and this is an issue that needs fixing asap.

smashah commented 3 years ago

What the hell is this library. I just had to JSON.parse the result twice for it to work as an object! In all of my years of JS, I've never seen this type of nonsense. Please fix this Chargebee. This is unacceptable when Stripe is already an option. Honestly.

return await new Promise((resolve, reject) => chargebee.customer.create({
email,
id
}).request((error: any, result: any) => {
if (error) {
    reject(error)
} else {
    resolve(JSON.parse(JSON.parse(JSON.stringify(`${result}`))))
}
})));
LaszloDev commented 3 years ago

Also agree that this library does not meet standards one would except.

@L-U-C-K-Y I'm also getting an empty response Result {} when creating a customer.

export interface ChargebeeCreateSubscriptionResponse {
subscription?: Subscription;
customer?: Customer;
card?: Card;
invoice?: Invoice;
unbilled_charges?: UnbilledCharge;
}

Thanks for the idea, though the types where also not exported as expected @L-U-C-K-Y


What the hell is this library. I just had to JSON.parse the result twice for it to work as an object! In all of my years of JS, I've never seen this type of nonsense. Please fix this Chargebee. This is unacceptable when Stripe is already an option. Honestly.

return await new Promise((resolve, reject) => chargebee.customer.create({
email,
id
}).request((error: any, result: any) => {
if (error) {
    reject(error)
} else {
    resolve(JSON.parse(JSON.parse(JSON.stringify(`${result}`))))
}
})));

Did some testing and using the node package chargebee seams to be better, you can still type the returned promise with putting together a type from the chargebee-typescript. Its not the best case but feels more reliable then the broken typescript libray. As of writing using version chargebee@2.6.4 and chargebee-typescript@2.0.7

import chargebee from 'chargebee'
import { ChargeBee as ChargeBeeType } from 'chargebee-typescript'

type ChargebeeCreateSubscriptionResponse = {
  subscription?: ChargeBeeType['subscription']
  customer?: ChargeBeeType['customer']
  card?: ChargeBeeType['card']
  invoice?: ChargeBeeType['invoice']
  unbilled_charges?: ChargeBeeType['unbilled_charge'][]
}

chargebee.configure({
  site: 'XX,
  api_key: 'XXX',
})

const yourFunctionForCreation = async () => {
    const result = await (<ChargebeeCreateSubscriptionResponse>(
      chargebee.subscription
        .create({
          plan_id: 'XXX',
          customer: { email: 'john2@user.com' },
        })
        .request()
    ))

    const { subscription, customer, card, invoice, unbilled_charges } = result

    console.log(
      'results spreaded',
      subscription,
      customer,
      card,
      invoice,
      unbilled_charges,
    )
}

image

rubas commented 3 years ago

Thanks for the helpful conversation.

Also agree that this library does not meet standards one would except.

This is sadly the issue with all their libraries. The quality is just bad ... and it's not getting any better. And don't get your hopes up. They are ignoring issues for 7 years and counting.

If you have the choice, stay away from Chargebee.

smashah commented 3 years ago

@rubas having an issue open that long would be embarrassing for me as a solo open source maintainer. I can't believe the issue you linked is still open, with open PRs as well?!?!, after so long on a repo of a handsomely funded company. I'm half way done with my stripe billing migration. Too bad. I really believed in chargebee.

ffxsam commented 3 years ago

I'm surprised they officially launched this (and included it in the official docs). It feels very much unfinished. Also, shocking that it still uses callback functions instead of just async/await. They're still even relying on the "Q" Promise library when it's not necessary!

I guess I'll go back to using the regular JS chargebee npm package. I'm also getting the empty Result {} response, so for me, it's unusable.

ffxsam commented 3 years ago

The code examples in the docs also don't make sense:

import {
  ChargeBee, 
  _estimate
} from 'chargebee-typescript';

var chargebee = new ChargeBee();

chargebee.configure({site : "{site}",
  api_key : "{site_api_key}"});
chargebee.estimate.upcoming_invoices_estimate("__test__KyVnHhSBWl2pw2aa").request(function(error,result) {
  if(error){
    //handle error
    console.log(error);
  }else{
    console.log(`${result}`);
    var estimate: typeof chargebee.estimate = result.estimate;
  }
});

Why import _estimate if it's not used?

cb-rakesh commented 3 years ago

We have made callback param optional in v2.2.0

dreinon commented 1 year ago

@LaszloDev @smashah Hi! Do you know if this issue is already fixed? Is the library working as expected? Otherwise, from your experience, what do you recommend? Would using the NodeJS library + hard-typing types from the typescript library be an option? Thanks in advance!