Search code examples
typescriptfirebasefluttergoogle-cloud-functionspayment

Problem with payment authorizing with Cielo Payment and Google Cloud functions


I'm developing an online shop, I have basically finished my application, at the moment I'm developing the payment part. My app consists on flutter, althought all my payment authorization and configs are stored in google cloud functions for safety reasons. For payments I'm using the Cielo Payment sandbox, with is a payment method that has sandbox so I can make my tests. I'm getting some error from my functions in google cloud with wasn't suppose to happen.

This is the website from cielo so I can acquire the keys for sandbox: https://cadastrosandbox.cieloecommerce.cielo.com.br/
The error I get:

Unhandled error TypeError: Cannot read property 'Code' of undefined

This is my index.ts:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import {CieloConstructor, Cielo,
  TransactionCreditCardRequestModel,
  EnumBrands, CaptureRequestModel} from "cielo";
// CaptureRequestModel, CancelTransactionRequestModel,
// TransactionCreditCardResponseModel

admin.initializeApp(functions.config().firebase);

// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript

const merchantId = functions.config().cielo.merchantid;
const merchantKey = functions.config().cielo.merchantkey;

const cieloParams: CieloConstructor = {
  merchantId: merchantId,
  merchantKey: merchantKey,
  sandbox: true,
  debug: true, // Remover quando for fazer transações reais
};

const cielo = new Cielo(cieloParams);

export const authorizeCreditCard = functions.https.
    onCall( async (data, context) => {
      if (data === null) {
        return {"success": false, "error":
        {"code": -1, "message": "Dados não informados"}};
      }

      if (!context.auth) {
        return {"success": false, "error":
        {"code": -1, "message": "Nenhum usuário logado"}};
      }

      const userId = context.auth.uid;

      const snapshot = await admin.firestore().collection("users").
          doc(userId).get();
      const userData = snapshot.data() || {};

      console.log("Iniciando autorização");

      let brand: EnumBrands;
      switch (data.creditCard.brand) {
        case "VISA":
          brand = EnumBrands.VISA;
          break;
        case "MASTERCARD":
          brand = EnumBrands.MASTER;
          break;
        case "AMEX":
          brand = EnumBrands.AMEX;
          break;
        case "ELO":
          brand = EnumBrands.ELO;
          break;
        case "JCB":
          brand = EnumBrands.JCB;
          break;
        case "DINERSCLUB":
          brand = EnumBrands.DINERS;
          break;
        case "DISCOVER":
          brand = EnumBrands.DISCOVERY;
          break;
        case "HIPERCARD":
          brand = EnumBrands.HIPERCARD;
          break;
        default:
          return {
            "success": false, "error": {
              "code": -1, "message": "Cartão não suportado: " +
              data.creditCard.brand,
            },
          };
      }

      const saleData: TransactionCreditCardRequestModel = {
        merchantOrderId: data.merchantOrderId,
        customer: {
          name: userData.name,
          identity: data.cpf,
          identityType: "CPF",
          email: userData.email,
          deliveryAddress: {
            street: userData.address.street,
            number: userData.address.number,
            complement: userData.address.complement,
            zipCode: userData.address.zipCode.replace(".", "").replace("-", ""),
            city: userData.address.city,
            state: userData.address.state,
            country: "BRA",
            district: userData.address.district,
          },
        },
        payment: {
          currency: "BRL",
          country: "BRA",
          amount: data.amount,
          installments: data.installments,
          softDescriptor: data.softDescriptor.substring(0, 13),
          type: data.paymentType,
          capture: false,
          creditCard: {
            cardNumber: data.creditCard.cardNumber,
            holder: data.creditCard.holder,
            expirationDate: data.creditCard.expirationDate,
            securityCode: data.creditCard.securityCode,
            brand: brand,
          },
        },
      };

      try {
        const transaction = await cielo.creditCard.transaction(saleData);

        if (transaction.payment.status === 1) {
          return {
            "success": true,
            "paymentId": transaction.payment.paymentId,
          };
        } else {
          let message = "";
          switch (transaction.payment.returnCode) {
            case "5":
              message = "Não Autorizada";
              break;
            case "57":
              message = "Cartão expirado";
              break;
            case "78":
              message = "Cartão bloqueado";
              break;
            case "99":
              message = "Timeout";
              break;
            case "77":
              message = "Cartão cancelado";
              break;
            case "70":
              message = "Problemas com o Cartão de Crédito";
              break;
            default:
              message = transaction.payment.returnMessage;
              break;
          }
          return {
            "success": false,
            "status": transaction.payment.status,
            "error": {
              "code": transaction.payment.returnCode,
              "message": message,
            },
          };
        }
      } catch (error) {
        return {
          "success": false,
          "error": {
            "code": error.response[0].Code,
            "message": error.response[0].Message,
          },
        };
      }
    });

export const captureCreditCard = functions.https.
    onCall( async (data, context) => {
      if (data === null) {
        return {"success": false, "error":
        {"code": -1, "message": "Dados não informados"}};
      }

      if (!context.auth) {
        return {"success": false, "error":
        {"code": -1, "message": "Nenhum usuário logado"}};
      }

      const captureParams: CaptureRequestModel = {
        paymentId: data.payId,
      };

      try {
        const capture = await cielo.creditCard.
            captureSaleTransaction(captureParams);

        if (capture.status == 2) {
          return {
            "success": true,
          };
        } else {
          return {
            "success": false,
            "status": capture.status,
            "error": {
              "code": capture.returnCode,
              "message": capture.returnMessage,
            },
          };
        }
      } catch (error) {
        return {
          "success": false,
          "error": {
            "code": error.response[0].Code,
            "message": error.response[0].Message,
          },
        };
      }
    });

My cielo_payment.dart:

import 'dart:collection';

import 'package:cloud_functions/cloud_functions.dart';
import 'package:flutter/cupertino.dart';
import 'package:loja_virtual_nnananene/models/credit_card.dart';
import 'package:loja_virtual_nnananene/models/user.dart';

class CieloPayment {
  final functions = CloudFunctions.instance;

  Future<String>? authorize(
      {CreditCard? creditCard, num? price, String? orderId, User? user}) async {
    try {
      final Map<String, dynamic> dataSale = {
        'merchantOrderId': orderId,
        'amount': (price! * 100).toInt(),
        'softDescriptor': 'Nnananene',
        'installments': 1,
        'creditCard': creditCard!.toJson(),
        'cpf': user!.cpf,
        'paymentType': 'CreditCard',
      };

      final HttpsCallable callable =
          functions.getHttpsCallable(functionName: 'authorizeCreditCard');

      callable.timeout = const Duration(seconds: 60);

      final response = await callable.call(dataSale);
      print(response.data);
      final data = Map<String, dynamic>.from(response.data);

      if (data['success'] as bool) {
        return data['paymentId'];
      } else {
        debugPrint('${data['error']['message']}');
        return Future.error(data['error']['message']);
      }
    } catch (e) {
      debugPrint('$e');
      return Future.error('Falha ao processar transação. Tente novamente.');
    }
  }

  Future<void> capture(String payId) async {
    final Map<String, dynamic> captureData = {'payId': payId};
    final HttpsCallable callable =
        functions.getHttpsCallable(functionName: 'captureCreditCard');
    callable.timeout = const Duration(seconds: 60);
    final response = await callable.call(captureData);
    final data = Map<String, dynamic>.from(response.data as LinkedHashMap);

    if (data['success'] as bool) {
      debugPrint('Captura realizada com sucesso');
    } else {
      debugPrint('${data['error']['message']}');
      return Future.error(data['error']['message']);
    }
  }

  Future<void> cancel(String payId) async {
    final Map<String, dynamic> cancelData = {'payId': payId};
    final HttpsCallable callable =
        functions.getHttpsCallable(functionName: 'cancelCreditCard');
    callable.timeout = const Duration(seconds: 60);
    final response = await callable.call(cancelData);
    final data = Map<String, dynamic>.from(response.data as LinkedHashMap);

    if (data['success'] as bool) {
      debugPrint('Cancelamento realizado com sucesso');
    } else {
      debugPrint('${data['error']['message']}');
      return Future.error(data['error']['message']);
    }
  }
}

My checkout_manager.dart where everything happens:

Future<void> checkout(
      {CreditCard? creditCard,
      Function? onStockFail,
      Function? onSuccess,
      Function? onPayFail}) async {
    loading = true;

    final orderId = await _getOrderId();

    try {
      String? payId = await cieloPayment.authorize(
        creditCard: creditCard,
        price: cartManager!.totalPrice,
        orderId: orderId.toString(),
        user: cartManager!.user,
      );
      debugPrint('success $payId');
    } catch (e) {
      onPayFail!(e);
      loading = false;
      return;
    }

    try {
      await _decrementStock();
    } catch (e) {
      onStockFail!(e);
      loading = false;
      return;
    }

enter image description here This are the lines the error refers to:

line 150:43 -  "code": error.respose[0].Code, // 43 refers to the dot in .Code
line 95:5 -  cardNumber: data.creditCard.cardNumber,


This is the error I get from my app, this is not a true credit card, and In the sandbox, when the card finishes with 1, is a successful transaction. This is the print from dataSale and response: enter image description here

After the fix: Fix

The error was at the validation from the card expiraation date, with had spaces causing an error


Solution

  • Change the following

     error.response[0].Code
     error.response[0].Message
    

    to this:

    error.code
    error.message
    

    The correct way to get an error's code and message in Node.js is by calling .code and .message on the error object itself.