Search code examples
phpstripe-paymentssilex

Stripe : handling charge/customer errors then insert data into DB in PHP


I am working with Stripe in Silex. Here, in my route I am getting data from the form, then I want to create a $customer and a $charge objects, then I want to save customer and order info into my database.

I also want to catch all possible errors with a try/catch as recommended in the Stripe doc, but I feel like my try/catch is way too long. I want to insert data into my database if the try is successful, but I don't know what's the best practice for that.

Here is my code :

$app->post('/{category}/{id}', function ($id) use ($app) {

$message = null;

$book = $app['dao.book']->findById($id);

$minAmount = $book->getPrice();
$paidAmount = floatval($_POST['amount']);

// check if value matches
$convMinAmount = ($book->getPrice()) * 100;
$convPaidAmount = (floatval($_POST['amount'])) * 100;

if (($convMinAmount > $convPaidAmount)) {
    $message = 'Veuillez saisir un montant plus élevé que le montant minimum s\'il vous plait.';
} else {
    $item_bought = $book->getTitle();
    $token = $_POST['stripeToken'];
    $stripeinfo = \Stripe\Token::retrieve($token);
    $email = $stripeinfo->email;

    $customer = \Stripe\Customer::create(array(
        'email' => $email,
        'source' => $token
    ));

    try {
        $charge = \Stripe\Charge::create(array(
            'customer' => $customer->id,
            'amount' => $paidAmount * 100,
            'currency' => "eur",
            'description' => 'Achat d\'un livre',
            'receipt_email' => $customer->email
        ));

        // Prepare values for insert into DB
        $full_name = $charge->source->name;
        $address = $charge->source->address_line1 . ', ' . $charge->source->address_city .
            ' ' . $charge->source->address_zip . ', ' . $charge->source->address_country;

        // Save order to DB
        $commande = new Commande();

        $commande->setName($full_name);
        $commande->setAdress($address);
        $commande->setItemBought($item_bought);
        $commande->setPricePaid($paidAmount);
        $commande->setEmail($email);

        $app['dao.commande']->save($commande);

        $message = 'Merci pour votre achat. Votre commande d\'un montant de ' . (floatval($charge->amount)) / 100 . '€ est validée. Un mail récapitulatif vous a été envoyé.';

     } catch (\Stripe\Error\Card $e) {
        echo($e->getMessage());
        // Since it's a decline, \Stripe\Error\Card will be caught
        $body = $e->getJsonBody();
        $err = $body['error'];
        $message = $err;
    } catch (\Stripe\Error\RateLimit $e) {
        // Too many requests made to the API too quickly
        $message = 'Too many requests, veuillez réessayer plus tard s\'il vous plait.';
    } catch (\Stripe\Error\InvalidRequest $e) {
        // Invalid parameters were supplied to Stripe's API
        print('Status is: ' . $e->getHttpStatus() . "\n");
        $message = 'Les paramètres sont invalides, veuillez s\'il vous plait contacter le webmaster du site.';
    } catch (\Stripe\Error\Authentication $e) {
        // Authentication with Stripe's API failed
        // (maybe you changed API keys recently)
        $message = 'L\'authentication à l\'API Stripe a échoué, veuillez s\'il vous plait contacter le webmaster du site.';
    } catch (\Stripe\Error\ApiConnection $e) {
        // Network communication with Stripe failed
        $message = 'La communication a échoué, veuillez réessayer plus tard s\'il vous plait.';
    } catch (\Stripe\Error\Base $e) {
        // Display a very generic error to the user, and maybe send
        // yourself an email
        $message = 'Il semblerait que la requête ait échoué, veuillez réessayer plus tard s\'il vous plait';
    } catch (Exception $e) {
        // Something else happened, completely unrelated to Stripe
        $message = 'Il semblerait que la requête ait échoué, veuillez réessayer plus tard s\'il vous plait';
    }
}

return $app['twig']->render('book.html.twig', array(
    'book' => $book,
    'message' => $message,
    'minAmount' => $minAmount
));

Finally I return a $message handling any case and I redirect the user to the book page. Sorry for the noob question, I am lost here. The code is working but I am not sure its the best way to go. Thanks for the help.


Solution

  • I have never used silex, but it should have a lot in common with Symfony. In symfony you can use event listener for it. Here you can see some examples on how to implement an event listener.

    The idea is to listen on exception event. Check if the exception is from Stripe and set a message according to what you need. Basically you just move this code to an event listener, so you can reuse it without code duplication. Then you can modify your example like:

    } catch (\Stripe\ErrorInterface $e) {
        $message = $e->getMessage();
    } catch (SomeDbException $dbException){
        ...
    }
    

    Assuming that all Stripe Errors exceptions implements the same interface. (Never used Stripe).

    I don't know if this is the best practice, but it can reduce and clear your code.

    EDIT Sorry I misunderstood your question. To your point. You should leave it as it is expecting that there can be some additional database exceptions. If you put it after try/catch block, it will insert data into the database even if there were an exception.

    But the best practice is to extract your code (persisting to the db) into a different service. This method should be responsible only for getting request and returning a response.

    So with the first point about events it could look like this:

    if (($convMinAmount > $convPaidAmount)) {
        $message = 'Veuillez saisir un montant plus élevé que le montant minimum s\'il vous plait.';
    } else {
        $item_bought = $book->getTitle();
        try{
            $stripeService->makeRequest($_POST['stripeToken'],...)
            $databaseService->insertCommande($charge, $fulname, $address,...)
    
    
            $message = 'Merci pour votre achat. Votre commande d\'un montant de ' . (floatval($charge->amount)) / 100 . '€ est validée. Un mail récapitulatif vous a été envoyé.';
    
        } catch (\Stripe\ErrorInterface $e) {
            $message = $e->getMessage();
        } 
    }
    
    return $app['twig']->render('book.html.twig', array(
        'book' => $book,
        'message' => $message,
        'minAmount' => $minAmount
    ));