SharmaPawan11 / vendure-razorpay-payment-plugin

Razorpay Plugin for Vendure
MIT License
7 stars 0 forks source link

Vendure Razorpay

Vendure Razorpay Plugin

A plugin to enable Razorpay as a payment provider for Vendure E-commerce

Vendure is released under the MIT license. PRs welcome! Follow @vendure_io PRs welcome!

๐ŸŒŸ Feature

This plugin have inside it a lot of stuff:

โš™๏ธ Install

1. Install and configure Vendure

Here you can find out how to install

2. Install the package

npm install vendure-razorpay-payment-plugin --save

3. Add the plugin in Vendure configuration

import { RazorpayPlugin } from 'vendure-razorpay-payment-plugin';
const config: VendureConfig = {
  ...
  plugins: [
    RazorpayPlugin
  ]
}

4. Configure RazorPay

You will need to enable and configure the options to make work. You can edit this in Payment Method section in Vendure Admin UI

5. Enjoy!

It's done!

โš™๏ธ Frontend Setup ( Angular )

1. Add razorpay script

<script src="https://checkout.razorpay.com/v1/checkout.js"></script>

or load it on-demand for performance reasons like this.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ScriptService {

  constructor() { }

  scripts = [
      {
        name: 'razorpay',
        src: 'https://checkout.razorpay.com/v1/checkout.js'
      },
    ]

  loadScript(name: string) {
    return new Promise((resolve, reject) => {

      const scriptObject = this.scripts.find((script) => {
        if (script.name === name) {
          return script;
        }
        return null;
      });

      if (scriptObject) {
        let script = document.createElement('script');
        script.src = scriptObject.src;
        script.onload = () => {
          resolve(true);
        };
        script.onerror = (error: any) => resolve(false);
        document.getElementsByTagName('head')[0].appendChild(script);
      } else {
        resolve(false);
      }
    });
  }
}
const isScriptLoaded = await this.scriptService.loadScript('razorpay');

2. Set checkout form options accordingly -

private _razorpayOptions = {
  key: environment.razorpayId,
  order_id: '',
  currency: 'INR',
  description: 'Description',
  // image: 'https://s3.amazonaws.com/rzp-mobile/images/rzp.png',
  prefill: {
    email: '',
    contact: '',
    name: '',
  },
  config: {
    display: {
      blocks: {
        card: {
          instruments: [
            {
              method: 'card',
              // issuers: ["UTIB"],
              networks: ['MasterCard', 'Visa', 'RuPay', 'Bajaj Finserv'],
            },
          ],
        },
        upi: {
          name: 'Pay using UPI',
          instruments: [
            {
              method: 'upi',
              flows: ['collect', 'intent', 'qr'],
              apps: ['google_pay', 'bhim', 'paytm', 'phonepe'],
            },
          ],
        },
        netbanking: {
          //  name for other block
          name: 'Pay using netbanking',
          instruments: [
            {
              method: 'netbanking',
            },
          ],
        },
        wallet: {
          name: 'Pay using wallets',
          instruments: [
            {
              method: 'wallet',
              wallets: ['phonepe', 'freecharge', 'airtelmoney'],
            },
          ],
        },
      },
      sequence: [
        'block.card',
        'block.upi',
        'block.netbanking',
        'block.wallet',
      ],
      preferences: {
        show_default_blocks: false,
      },
    },
  },
  handler: function (response: any) {
    console.log(response);
  },
  modal: {
    ondismiss: function () {},
  },
};

3. Generate Razorpay order id by calling mutation from plugin ( Order must be in "ArrangingPayment" state )

razorpayService.ts

const GENERATE_RAZORPAY_ORDER_ID = gql`
  mutation generateRazorpayOrderId($vendureOrderId: ID!) {
    generateRazorpayOrderId(orderId: $vendureOrderId) {
      __typename
      ... on RazorpayOrderIdSuccess {
        razorpayOrderId
      }
      ... on RazorpayOrderIdGenerationError {
        errorCode
        message
      }
    }
  }
  `;

generateRazorpayOrderId(vendureOrderId: string | number) {
  return this.requestor
    .mutate(GENERATE_RAZORPAY_ORDER_ID, {
      vendureOrderId,
    })
    .pipe(map((res) => res.generateRazorpayOrderId)
}

checkout.component.ts

this.orderService.generateRazorpayOrderId(this.orderDetails.id).pipe(
  this.updateOrderDetailsGlobally.operator(),
    takeUntil(this.destroy$)
  )
  .subscribe((res) => {
    this.onRazorpayIdGeneration(res);
});

onRazorpayIdGeneration(res: any) {
  if (res.__typename === 'RazorpayOrderIdSuccess') {
    const razorpayOrderId = res.razorpayOrderId;
    this.openRazorpayPopup(razorpayOrderId); // Implemented below
  } else {
    console.log(
      'Some error occurred while generating Razorpay orderId',
      res.message,
      res.errorCode
    );
    this.razorpayFlowActive = false;
    this.cd.detectChanges();
  }
}

4. Get Razorpay class

get Razorpay() {
  if (!(window as any).Razorpay) {
    throw new Error(
      'Can\'t find razorpay. Make sure you have added <script src="https://checkout.razorpay.com/v1/checkout.js"></script> in your index.html file'
    );
  }
  return (window as any).Razorpay;
}

5. Construct success and manualClose callbacks

    5.1 Get a reference to Angular changeDetector and NgZone

    checkout.component.ts

constructor(
    ...
    private cd: ChangeDetectorRef,
    private zone: NgZone
    ...
  ) {
    ...
  }

    5.2 Construct the callbacks

    checkout.component.ts

onRazorpayPaymentSuccess(metadata: Object) {
  this.cd.detectChanges();
  this.orderService
    .addRazorpayPaymentToOrder(metadata)
    .pipe(
      takeUntil(this.destroy$))
    .subscribe((res) => {
      switch (res.__typename) {
        case 'PaymentFailedError':
        case 'PaymentDeclinedError':
        case 'IneligiblePaymentMethodError':
        case 'OrderPaymentStateError':
          console.log(res.errorCode, res.message);
          break;
        case 'Order':
          console.log('PAYMENT SUCCESSFUL');
          this.zone.run(() => {
            this.router.navigate(['..'], {
              relativeTo: this.route,
            });
          })
      }
    });
}

onRazorpayManualClose() {
  if (confirm('Are you sure, you want to close the form?')) {
    console.log('Checkout form closed by the user');
    this.razorpayFlowActive = false;
    this.cd.detectChanges();
  } else {
    console.log('Complete the Payment');
  }
}

    razorpayService.ts

addRazorpayPaymentToOrder(paymentMetadata: Object): Observable<Mutation["addPaymentToOrder"]> {
  const addPaymentMutationVariable = {
    paymentInput: {
      method: 'razorpay',
      metadata: JSON.stringify(paymentMetadata),
    },
  };
  return this.requestor
    .mutate(
      ADD_PAYMENT_TO_ORDER_MUTATION,
      addPaymentMutationVariable
    )
    .pipe(map((res) => res.addPaymentToOrder));
}

    where ADD_PAYMENT_TO_ORDER_MUTATION is -

const ADD_PAYMENT_TO_ORDER_MUTATION = gql`
  mutation addPaymentToOrder($paymentInput: PaymentInput!) {
    addPaymentToOrder(input: $paymentInput) {
      __typename
      ... on Order {
        id
      }
      ... on PaymentFailedError {
        errorCode
        paymentErrorMessage
        message
      }
      ... on OrderPaymentStateError {
        errorCode
        message
      }
      ... on PaymentDeclinedError {
        message
        paymentErrorMessage
        errorCode
      }
    }
  }
`;

6. Set required things for checkout form options ( Sample code )

razorpayService.ts

get razorpayOptions() {
  return this._razorpayOptions;
}

set razorpayOrderId(orderId: string) {
  this._razorpayOptions.order_id = orderId;
}

set razorpayPrefill({
  email,
  contact,
  name,
}: {email: string | null, contact: string | null, name: string | null}) {
  email = email || '';
  contact = contact || '';
  name = name || '';
  this._razorpayOptions.prefill = {
    email, contact, name
  };
}

checkout.component.ts

openRazorpayPopup(razorpayOrderId: string) {
  try {
    const Razorpay = this.razorpayService.Razorpay;
    this.razorpayService.razorpayOrderId = razorpayOrderId;
    this.razorpayService.razorpayPrefill = {
      contact: this.customerDetails.customerPhNo,
      email: this.customerDetails.customerEmail,
      name: this.customerDetails.customerName,
    };
    this.razorpayService.razorpaySuccessCallback =
      this.onRazorpayPaymentSuccess.bind(this);
    this.razorpayService.razorpayManualCloseCallback =
      this.onRazorpayManualClose.bind(this);
    const rzp = new Razorpay(this.razorpayService.razorpayOptions);
    rzp.on('payment.failed', (response: any) => {
      // console.log(response);
    });
    rzp.open();
  } catch (e) {
    console.log(e);
  }
}

Check razorpay docs here

๐Ÿ˜ Do you like?

Please, consider supporting my work as a lot of effort takes place to create this repo! Thanks a lot.

Buy Me A Coffee

โ—๏ธ License

MIT