From c0d2e11172a14e0feb119a1134ecb370a432b511 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Fri, 22 Jul 2016 09:01:39 +1000 Subject: [PATCH] further implementation work for stripe integration --- booking.admin.inc | 4 +- booking.helper.inc | 134 ++++++++++++++++------------- booking.install | 14 ++- booking.paypal.inc | 50 ++--------- booking.stripe.inc | 206 ++++++++++++++++++++------------------------- booking.stripe.js | 1 + 6 files changed, 188 insertions(+), 221 deletions(-) diff --git a/booking.admin.inc b/booking.admin.inc index 0261ffb..bd007d8 100644 --- a/booking.admin.inc +++ b/booking.admin.inc @@ -253,8 +253,8 @@ function booking_admin() { $form['features']['booking_payment_processor'] = array ( '#type' => 'radios', '#title' => t('Select Payment Processor to use'), - '#description' => t('Select between Paypal and Stripe to use for integrated payment methods. Has no impact if manual payments are being used.'), - '#options' => array (0 => t('Paypal'), t('Stripe')), + '#description' => t('Select between Paypal, Stripe or Manaul Payment to use for processing payments.'), + '#options' => array (0 => t('Paypal'), t('Stripe'), t('Manual Payments')), '#default_value' => variable_get('booking_payment_processor', 0), ); $form['features']['booking_enable_roomallocations'] = array( diff --git a/booking.helper.inc b/booking.helper.inc index 96e609d..13badb7 100644 --- a/booking.helper.inc +++ b/booking.helper.inc @@ -871,13 +871,11 @@ function _booking_deposit_amount($person, $include_fees = TRUE) /** * Calculate exactly how much a person has already paid */ -function _booking_amount_paid($nid, $person = NULL) -{ +function _booking_amount_paid($nid, $person = NULL) { $amount_paid = 0; //fetch details about the person if we don't already have them - if ($person == NULL) - { + if ($person == NULL) { $person = db_query("SELECT price.booking_price, price.booking_late_price, person.booking_payment_id, " . "person.booking_total_pay_reqd, person.booking_amount_paid, person.booking_partner_id " . "FROM {booking_person} person, {booking_price} price " . @@ -888,15 +886,13 @@ function _booking_amount_paid($nid, $person = NULL) } //check for a spouse - if ($person->booking_partner_id > 0) - { + if ($person->booking_partner_id > 0) { //watchdog('booking', "Checking total paid for married person"); //look for payments in the payment transaction table //if we're combining the payment for married couples - if (variable_get('booking_enable_combined_pricing', 0) == 1) - { + if (variable_get('booking_enable_combined_pricing', 0) == 1) { //check for payments in the paypal transaction table from either spouse $query = db_query("SELECT booking_mc_gross, booking_mc_fee FROM booking_payment " . "WHERE booking_person_nid = :nid OR booking_person_nid = :spousenid", @@ -910,17 +906,16 @@ function _booking_amount_paid($nid, $person = NULL) } //add up all the payments - foreach ($query as $payment) + foreach ($query as $payment) { $amount_paid += ($payment->booking_mc_gross - $payment->booking_mc_fee); + } //watchdog('booking', "Total amount paid according to paypal payments table is " . $amount_paid); //there were no payment transactions, so rely on what the amount is set to in the individual registration records - if ($amount_paid == 0) - { - //if we're combining the payment for married couples - if (variable_get('booking_enable_combined_pricing', 0) == 1) - { + if ($amount_paid == 0) { + //if we're combining the payment for married couples + if (variable_get('booking_enable_combined_pricing', 0) == 1) { $spouse = db_query("SELECT person.booking_amount_paid " . "FROM {booking_person} person " . "WHERE person.nid = :nid ", @@ -929,17 +924,15 @@ function _booking_amount_paid($nid, $person = NULL) $amount_paid = $person->booking_amount_paid + $spouse->booking_amount_paid; //watchdog('booking', "Total combined payments for couple based on totals in booking_person table is " . $amount_paid); } - else - { - $amount_paid = $person->booking_amount_paid; + else { + $amount_paid = $person->booking_amount_paid; //watchdog('booking', "Total amount paid for individual based on totals in booking_person table is " . $amount_paid); } //end combined payments check } } //no spouse found - else - { + else { //Check if there are payment records in the paypal transaction table for this unmarried person //if so, base their payments on the gross amount in there rather than what the person booking_person database says $query = db_query("SELECT booking_mc_gross, booking_mc_fee FROM booking_payment " . @@ -951,8 +944,9 @@ function _booking_amount_paid($nid, $person = NULL) //watchdog('booking', "Total amount paid according to paypal payments table is " . $amount_paid); //if there were no results, $amount_paid will still be 0 - if ($amount_paid == 0) + if ($amount_paid == 0) { $amount_paid = $person->booking_amount_paid; + } //in case there has been some manual adjustment //elseif ($amount_paid < $person->booking_amount_paid) // $amount_paid = $person->booking_amount_paid; @@ -1021,26 +1015,11 @@ function _booking_total_due($person) * * @param $person - the object relating to the registration node * @param $amount_paid - if we have previously calculated the amount paid, this can be passed as a value - * @param $include_paypal_fees - boolean to indicate whether we want the net amount or the amount owing including paypal fees + * @param $include_fees - boolean to indicate whether we want the net amount or the amount owing including paypal fees * @return amount owing as a decimal value */ -function _booking_amount_owing($person, $amount_paid = 0, $include_paypal_fees = TRUE) -{ - //$amount_paid = 0; - //$total_due = 0; - - //fetch details about the person - /* - $person = db_query("SELECT price.booking_price, price.booking_late_price, person.booking_payment_id, " . - "person.booking_total_pay_reqd, person.booking_amount_paid, person.booking_partner_id, person.booking_country, person.booking_welfare_required, person.booking_committee_member " . - "FROM {booking_person} person, {booking_price} price " . - "WHERE person.nid = :nid " . - "AND person.booking_payment_id = price.pid", - array(':nid' => $nid)) - ->fetchObject(); - - //$person = node_load($nid); - */ +function _booking_amount_owing($person, $amount_paid = 0, $include_fees = TRUE) { + global $event; //quick sanity check if (! $person->nid) { watchdog('booking', "Unable to find matching person relating to registration id. Details were '" . var_export($person, TRUE) . "' ."); @@ -1050,43 +1029,80 @@ function _booking_amount_owing($person, $amount_paid = 0, $include_paypal_fees = $total_due = _booking_total_due($person); //if we didn't get the amount paid as a command line parameter, check it now - if ($amount_paid == 0) - { + if ($amount_paid == 0) { $amount_paid = _booking_amount_paid($person->nid, $person); } //check if there is anything outstanding //use the original total amount required rather than the late-rate, to cater for people that paid before the late rate - if ($amount_paid >= $person->booking_total_pay_reqd) - { + if ($amount_paid >= $person->booking_total_pay_reqd) { //watchdog('booking', "This person doesn't owe any money: @info", array('@info' => var_export($person, TRUE))); return 0; } + //@todo - use booking_payment_processor instead. 0 is paypal, 1 is stripe, 2 is manual + //if we're using paypal, add the transaction fee - if (variable_get('booking_use_paypal', 0) == 1 && $include_paypal_fees == TRUE) - { + if (variable_get('booking_use_paypal', 0) == 1 && $include_fees == TRUE) { $amount_owing = _booking_add_paypal_fees($total_due - $amount_paid, $person->booking_country); - /* - //add the 30 cent fixed cost - $amount_owing = $total_due - $amount_paid + 0.3; - //and the 2.4 percent transaction fee - if ($person->booking_country === "Australia") - $amount_owing = $amount_owing / (1 - 0.026); - //paypal charges 3.4 percent if they're doing a currency conversion - //assume that everyone not based in Australia will be doing a currency conversion - else - { - $amount_owing = $amount_owing / (1 - 0.036); - //watchdog('booking', "This is an international registration."); - } - */ } - else + else { $amount_owing = $total_due - $amount_paid; + } return number_format($amount_owing, 2, '.', ''); } +/** + * Function to update the person object when a payment has been made + * payment details should be inserted into booking_payment prior to calling this function + * + * @param $person - the populated node object representing this person including related price information + * @param $payment_amount - the gross payment amount that is being processed + * @param $balance_payment - boolean to indicate if this transaction was to complete a payment + * @return nothing + */ +function _booking_process_payment($person, $payment_amount, $balance_payment) { + global $event; + + //calculate their total payment amount + $total = $person->booking_amount_paid + $payment_amount; + + //only recalculate their booking status if this is the initial payment, not a payment of the outstanding balance + if ($balance_payment == FALSE) { + watchdog('booking', 'Processing an initial payment. Booking status is currently ' . $person->booking_status); + if ($person->booking_status == 1) { + watchdog('booking', 'This registration has been manually assigned to the booked-in list.'); + $status = 1; + } + elseif (_booking_check_bookings_full() == True || $person->booking_status == 2) { + watchdog('booking', 'This registration belongs on the waiting list.'); + $status = 2; + } + else { + watchdog('booking', 'This registration made it to the booked-in list.'); + $status = 1; + } + } + else { + //this is a balance payment + watchdog('booking', 'Processing a balance payment.'); + //if this is a payment of outstanding balance, keep the booking_status the same + $status = $person->booking_status; + } + + //update the database for this person + db_update('booking_person') + ->fields(array( + 'booking_amount_paid' => $total, + 'booking_status' => $status, + )) + ->condition('nid', $nid) + ->execute(); + + //handle workflow emails and spouse payment updates + _booking_postpayment_trigger($nid, $person, $balance_payment); +} + /** * Function to handle any post-payment notification emails for either paypal or manual payment * still a WIP diff --git a/booking.install b/booking.install index 9cae246..7b8305e 100644 --- a/booking.install +++ b/booking.install @@ -605,6 +605,16 @@ function booking_update_7239() { _booking_node_create_mysqlview(); } +/** +* Add stripe token field to booking_payments table +*/ +function booking_update_7240() { + $spec = array('type' => 'varchar', 'length' => '200', 'not null' => FALSE); + db_add_field('booking_payment', 'booking_stripe_token', $spec); + //update the view to match the new table definition + _booking_node_create_mysqlview(); +} + /** * Implementation of hook_install(). */ @@ -624,8 +634,8 @@ function booking_install() { //earlybird close is 31st Jan 2012 at 13:59:59 UTC $result = db_insert('booking_event') ->fields(array( - 'booking_eventname' => 'Sample Event', - 'booking_event_active' => 1, + 'booking_eventname' => 'Sample Event', + 'booking_event_active' => 1, 'booking_register_open' => 1312207199, 'booking_register_close' => 1340459999, 'booking_earlybird_close' => 1328018399, diff --git a/booking.paypal.inc b/booking.paypal.inc index d878089..a59dbcb 100644 --- a/booking.paypal.inc +++ b/booking.paypal.inc @@ -91,7 +91,7 @@ function booking_paypal_ipn() { } */ - //TODO: Handle refund and payment dispute IPNs + //@todo Handle refund and payment dispute IPNs if (empty($ipn['payment_status']) || ($ipn['payment_status'] != 'Completed' && variable_get('booking_paypal_sandbox', 0) == 0)) return; @@ -131,8 +131,7 @@ function _booking_process_payment($data) { //verify paypal hasn't already sent through a notification for this payment $duplicate_check = db_query("SELECT payid, booking_person_nid FROM {booking_payment} where booking_ipn_track_id = :ipn_id ", - array(':ipn_id' => $data['ipn_track_id'])) - ->fetchObject(); + array(':ipn_id' => $data['ipn_track_id']))->fetchObject(); if ($duplicate_check) { watchdog('booking', 'Detected duplicate paypal notifications for transaction id !id, registration id !nid', array('!id' => $data['ipn_track_id'], '!nid' => $nid), WATCHDOG_ERROR); return; @@ -166,45 +165,9 @@ function _booking_process_payment($data) { //check if we found a person matching this payment if ($person) { - watchdog('booking', 'Found matching user with node id: !id; event id: !eid; existing payment !payment', array('!id' => $nid, '!eid' => $eid, - '!payment' => $person->booking_amount_paid)); - //if successful, update their total payment amount - $total = $person->booking_amount_paid + $data['mc_gross']; - - //only recalculate their booking status if this is the initial payment, not a payment of the outstanding balance - if ($balance_payment == FALSE) { - watchdog('booking', 'Processing an initial payment. Booking status is currently ' . $person->booking_status); - if ($person->booking_status == 1) { - watchdog('booking', 'This registration has been manually assigned to the booked-in list.'); - $status = 1; - } - elseif (_booking_check_bookings_full() == True || $person->booking_status == 2) { - watchdog('booking', 'This registration belongs on the waiting list.'); - $status = 2; - } - else { - watchdog('booking', 'This registration made it to the booked-in list.'); - $status = 1; - } - } - else { - //this is a balance payment - watchdog('booking', 'Processing a balance payment.'); - //if this is a payment of outstanding balance, keep the booking_status the same - $status = $person->booking_status; - } - - //update the database for this person - db_update('booking_person') - ->fields(array( - 'booking_amount_paid' => $total, - 'booking_status' => $status, - )) - ->condition('nid', $nid) - ->execute(); - - //handle workflow emails and spouse payment updates - _booking_postpayment_trigger($nid, $person, $balance_payment); + watchdog('booking', 'Found matching user with node id: !id; event id: !eid; existing payment !payment', + array('!id' => $nid, '!eid' => $eid, '!payment' => $person->booking_amount_paid)); + _booking_process_payment($person, $data['mc_gross'], $balance_payment); } else { //couldn't find a matching nid for this invoice @@ -216,8 +179,7 @@ function _booking_process_payment($data) { /** * Landing page after returning from paypal */ -function booking_payment_completed_page() -{ +function booking_payment_completed_page() { //get some configuration information global $event; $output = ""; diff --git a/booking.stripe.inc b/booking.stripe.inc index dcfda89..623a964 100644 --- a/booking.stripe.inc +++ b/booking.stripe.inc @@ -4,6 +4,7 @@ * @file * Functions for stripe payment integration * @see https://github.com/ericthelast/drupal-stripe-form and https://www.webomelette.com/drupal-stripe-integration + * Australian test number is 4000000360000006 */ /** @@ -43,9 +44,6 @@ function booking_stripe_form($node, &$form_state, $person, $invoiceid, $amount_o $settings = array(); $form = array(); - //get our current path so we can send the user back here if they cancel - //$path = isset($_GET['q']) ? $_GET['q'] : ''; - //set some values for our internal stripe library to help process the form //these will be used by the attached js from booking-strip library to identify which parts of the form to process @@ -116,116 +114,6 @@ function booking_stripe_form($node, &$form_state, $person, $invoiceid, $amount_o return $form; } - - -/* -function booking_stripe_form($form, &$form_state, $person, $invoiceid, $amount_owing, $button_text) { - global $event; - $setting = array(); - $form = array(); - - // Try to load the library and check if that worked. - if (($library = libraries_load('stripe')) && !empty($library['loaded'])) { - // Do something with the library here. - } - else { - //library can be downloaded from https://stripe.com/docs/libraries - form_set_error('form', t('The required stripe library is not installed. Please contact your site adaministrator')); - } - - //set some values for our internal stripe library to help process the form - //these will be used by the attached js from booking-strip library to identify which parts of the form to process - $setting['booking_stripeform'] = array( - 'pubkey' => _booking_get_stripe_public_key(), - 'form_selector' => str_replace('_', '-', __FUNCTION__), - ); - $form['#attached'] = array( - 'js' => array( - array('data' => $setting, 'type' => 'setting'), - ), - 'library' => array( - array('booking', 'booking-stripe'), - ), - ); - - $form['stripeToken'] = array( - '#type' => 'hidden', - '#value' => !empty($form_state['input']['stripeToken']) ? $form_state['input']['stripeToken'] : NULL, - ); - - $form['amount'] = array( - '#type' => 'hidden', - '#value' => $amount_owing, - ); - - $form['credit_card'] = array( - '#type' => 'fieldset', - '#title' => t('Credit Card Information'), - '#description' => t('

This credit card information is securely processed via stripe.com. No credit card information is stored or processed on our server.

'); - ); - $cc = &$form['credit_card']; - - $cc['card_number'] = array( - '#type' => 'textfield', - '#title' => t('Credit Card Number'), - '#pre_render' => array('booking_stripeform_remove_name'), - '#attributes' => array( - 'size' => 20, - 'data-stripe' => 'number', - ), - ); - - $cc['exp_month'] = array( - '#type' => 'select', - '#title' => t('Expiration Month'), - '#options' => drupal_map_assoc(array(1,2,3,4,5,6,7,8,9,10,11,12)), - '#pre_render' => array('booking_stripeform_remove_name'), - '#attributes' => array( - 'data-stripe' => 'exp-month', - ), - '#empty_option' => t('- Select -'), - ); - - $cc['exp_year'] = array( - '#type' => 'select', - '#title' => t('Expiration Year'), - '#options' => array(), - '#pre_render' => array('booking_stripeform_remove_name'), - '#attributes' => array( - 'data-stripe' => 'exp-year', - ), - '#empty_option' => t('- Select -'), - ); - - $year = date('Y'); - for($i = $year; $i <= ($year + 10); $i++) { - $cc['exp_year']['#options'][$i] = $i; - } - - $cc['cvc'] = array( - '#type' => 'textfield', - '#title' => t('CVC Number'), - '#pre_render' => array('booking_stripeform_remove_name'), - '#attributes' => array( - 'size' => 4, - 'data-stripe' => 'cvc', - ), - ); - - $cc['submit'] = array( - '#type' => 'submit', - '#value' => t('Charge It'), - '#attributes' => array( - 'class' => array('btn', 'btn-large', 'btn-primary'), - ), - ); - - // Adds our validation at the end of the build process. - $form['#after_build'][] = 'booking_stripe_add_final_validation'; - return $form; -} -*/ - /** * Tries to add final validation after all else has been added through alters. */ @@ -267,6 +155,8 @@ function booking_stripe_validate_form_payment($form, &$form_state) { $invoice = (isset($form_state['input']['invoice']) ? $form_state['input']['invoice'] : ''); $nid = (isset($form_state['input']['nid']) ? $form_state['input']['nid'] : ''); $tempid= (isset($form_state['input']['uuid']) ? $form_state['input']['uuid'] : ''); + $last_name = (isset($form_state['input']['last_name']) ? $form_state['input']['last_name'] : ''); + $first_name = (isset($form_state['input']['first_name']) ? $form_state['input']['first_name'] : ''); watchdog('booking_debug', "
Stripe payment form :\n@info
", array('@info' => print_r( $form_state, true))); // Create the charge on Stripe's servers - this will charge the user's card @@ -282,12 +172,15 @@ function booking_stripe_validate_form_payment($form, &$form_state) { "metadata" => array( "invoice" => $invoice, "nid" => $nid, + "last_name" => $last_name, + "first_name" => $first_name, ), )); watchdog('booking_debug', "
Stripe payment charge results:\n@info
", array('@info' => print_r( $charge, true))); if ($charge && $charge->paid) { watchdog('booking', 'Charge created successfully'); - $form_state['stripeform_charge'] = $charge; + _booking_process_stripe_payment($charge); + //$form_state['stripeform_charge'] = $charge; // @todo call _booking_process_stripe_payment to store payment drupal_goto('bookingfinal/' . $tempid); } @@ -327,4 +220,89 @@ function booking_stripeform_form_submit($form, &$form_state) { function booking_stripeform_remove_name($element) { unset($element['#name']); return $element; +} + + +function _booking_process_stripe_payment($charge) { + global $event; + $balance_payment = false; + $amount_owing = 0; + //$invoice = $data->metadata; + + //verify the status of the charge + if (empty($charge->status) || ($charge->status != 'succeeded')) { + $successful = FALSE; + } + else { + $successful = TRUE; + } + + //extract the person node id from the invoice + $pos = strpos($charge->metadata->invoice, "_"); + if (($pos === false) || ($pos == 0)) { + watchdog('booking', 'Unable to process payment with invalid invoice information: !id', array('!id' => $charge->metadata->invoice), WATCHDOG_ERROR); + return; + } + + //get the part of the invoice up to the first underscore + $nid = substr($charge->metadata->invoice, 0, $pos); + //get the data between the first and second underscore + $eid = substr($charge->metadata->invoice, $pos + 1, strrpos($charge->metadata->invoice, "_") - $pos - 1); + + if (substr($eid,0,3) == "bal") { + $balance_payment = true; + watchdog('booking_debug', 'Balance payment via stripe for user with node id: !id and status !status.', + array('!id' => $nid, '!status' => $charge->status)); + } + else { + watchdog('booking_debug', 'Initial payment via stripe for user with node id: !id and status !status.', + array('!id' => $nid, '!status' => $charge->status)); + } + + //this shouldn't ever happen, since stripe is sending this notification synchronously + //but just in case, check for an existing transaction that matches this one + $duplicate_check = db_query("SELECT payid, booking_person_nid FROM {booking_payment} where booking_ipn_track_id = :ipn_id ", + array(':ipn_id' => $charge->id))->fetchObject(); + + if ($duplicate_check) { + watchdog('booking', 'Detected duplicate stripe transaction notifications for transaction id !id, registration id !nid', + array('!id' => $charge->id, '!nid' => $nid), WATCHDOG_ERROR); + return; + } + + $gross_amount = $charge->amount / 100; + $result = db_insert('booking_payment') + ->fields(array( + 'booking_person_nid' => $nid, + 'booking_eventid' => $event->eid, + 'booking_mc_gross' => $gross_amount, + 'booking_mc_currency' => $charge->balance_transaction->currency, + 'booking_mc_fee' => $charge->balance_transaction->fee / 100, + 'booking_invoice' => $charge->metadata->invoice, + 'booking_payer_id' => $charge->source->id, + 'booking_payment_date' => $charge->created, + 'booking_payment_status' => $charge->status, + 'booking_first_name' => $charge->metadata->first_name, + 'booking_last_name' => $charge->metadata->last_name, + 'booking_buyer_email' => $charge->receipt_email, + //'booking_payer_status' => $data['payer_status'], + 'booking_item_name' => $data->description, + 'booking_ipn_track_id' => $data->id, + )) + ->execute(); + + //Get the person's info so we can update their total amount paid and booking status + $person = node_load($nid); + + //check if we found a person matching this payment + if ($person) { + watchdog('booking', 'Found matching user with node id: !id; event id: !eid; existing payment !payment', + array('!id' => $nid, '!eid' => $eid, '!payment' => $person->booking_amount_paid)); + _booking_process_payment($person, $gross_amount, $balance_payment); + } + else { + //couldn't find a matching nid for this invoice + watchdog('booking', "Unable to process payment for user with node id: '!id'", array('!id' => $nid), WATCHDOG_ERROR); + //db_query("UPDATE {booking_person} SET booking_tempid='' WHERE nid = %d", $nid); + } } \ No newline at end of file diff --git a/booking.stripe.js b/booking.stripe.js index fbbe60a..8a2d79a 100644 --- a/booking.stripe.js +++ b/booking.stripe.js @@ -27,6 +27,7 @@ jQuery(document).ready(function($) { email: currentForm.find('input[name="email"]').val(), currency: "aud", amount: currentForm.find('input[name="amount"]').val() * 100, + zipCode: true, closed: function() { //document.getElementById("booking_stripe_form").submit(); }