<?php
/**
* My Calendar Submission PayPal Gateway
*
* @category Payments
* @package My Calendar Pro
* @author Joe Dolson
* @license GPLv2 or later
* @link https://www.joedolson.com/my-calendar-pro/
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Insert PayPal form
*
* @param string $form Form HTML.
* @param string $gateway Name of gateway.
* @param array $args Gateway arguments.
*
* @return string
*/
function mcs_paypal_form( $form, $gateway, $args ) {
if ( 'paypal' === $gateway ) {
$form .= mcs_paypal_insert_form( $args['price'], $args['currency'], $args['discount_rate'], $args['discounts'], $args['discount'] );
}
return $form;
}
add_filter( 'mcs_payment_gateway', 'mcs_paypal_form', 10, 3 );
/**
* Provide response message from PayPal.
*
* @param array $response Data from PayPal.
* @param string $mcs Query string value.
* @param array $payment Payment data.
* @param string $gateway Gateway name.
*
* @return string
*/
function mcs_paypal_response( $response, $mcs, $payment, $gateway ) {
$ret = '';
if ( 'paypal' === $gateway ) {
$hash = $payment['hash'];
$receipt = add_query_arg( 'mcs_receipt', $hash, home_url() );
switch ( $mcs ) {
case 'thanks':
// Translators: Receipt link.
$ret = "<p class='notice'>" . sprintf( __( 'Thank you for your purchase! You can view your purchase information at PayPal. You will receive an email with your payment key once your payment is finalized. %s', 'my-calendar-pro' ), '<a href="' . $receipt . '">Receipt</a>' ) . '</p>';
break;
case 'cancel':
$ret = __( 'Sorry that you decided to cancel your purchase! Contact us if you have any questions!', 'my-calendar-pro' );
break;
}
}
return $ret;
}
add_filter( 'mcs_gateway_response', 'mcs_paypal_response', 10, 4 );
/**
* Process payment from PayPal.
*
* @param string $gateway Gateway.
*/
function mcs_paypal_processing( $gateway ) {
if ( isset( $_POST['payment_status'] ) ) {
$sandbox = get_option( 'mcs_use_sandbox' );
$receiver = strtolower( get_option( 'mcs_paypal_email' ) );
$url = ( 'true' === $sandbox ) ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr';
$req = 'cmd=_notify-validate';
foreach ( $_POST as $key => $value ) {
$value = urlencode( stripslashes( $value ) );
$req .= "&$key=$value";
}
$args = wp_parse_args( $req, array() );
$params = array(
'body' => $args,
'timeout' => 30,
'user-agent' => 'WordPress/My Calendar Pro',
'httpversion' => '1.1',
);
// transaction variables to store.
$payment_status = sanitize_text_field( $_POST['payment_status'] );
$item_number = sanitize_text_field( $_POST['item_number'] );
$price = sanitize_text_field( $_POST['mc_gross'] );
$payment_currency = sanitize_text_field( $_POST['mc_currency'] );
$receiver_email = sanitize_text_field( $_POST['receiver_email'] );
$payer_email = sanitize_text_field( $_POST['payer_email'] );
$payer_first = sanitize_text_field( $_POST['first_name'] );
$payer_last = sanitize_text_field( $_POST['last_name'] );
$mc_fee = sanitize_text_field( $_POST['mc_fee'] );
$txn_id = sanitize_text_field( $_POST['txn_id'] );
// paypal IPN data.
$ipn = wp_remote_post( $url, $params );
if ( is_wp_error( $ipn ) ) {
// send an email notification about this error.
wp_mail( get_option( 'mcs_to' ), __( 'My Calendar Pro could not contact PayPal', 'my-calendar-pro' ), __( 'Here are the results of the query to PayPal:', 'my-calendar-pro' ) . "\n\n" . print_r( $ipn, 1 ) );
status_header( 503 );
die;
}
$response = $ipn['body'];
$response_code = $ipn['response']['code'];
$currency = get_option( 'mcs_currency' );
// Placeholder; need to change the order creation process for this to work.
$expected = $price;
$value_match = ( (float) $price === (float) $expected ) ? true : false;
// die conditions for PayPal.
// if receiver email or currency are wrong, this is probably a fraudulent transaction.
if ( strtolower( $receiver_email ) !== $receiver || $payment_currency !== $currency ) {
$error_msg = array();
$messages = '';
if ( ! $value_match ) {
// Translators: 1) Price passed, 2) Price expected.
$error_msg[] = sprintf( __( 'Price paid did not match the price expected: %1$s vs %2$s', 'my-calendar-pro' ), $price, $expected );
}
if ( strtolower( $receiver_email ) !== $receiver ) {
// Translators: 1) Email expected, 2) Email received.
$error_msg[] = sprintf( __( 'Receiver Email and PayPal Email did not match: %1$s vs %2$s', 'my-calendar-pro' ), $receiver_email, $receiver );
}
if ( $payment_currency !== $currency ) {
// Translators: 1) currency passed, 2) Currency expected.
$error_msg[] = sprintf( __( 'Currency received did not match the currency expected: %1$s vs %2$s', 'my-calendar-pro' ), $payment_currency, $currency );
}
foreach ( $error_msg as $msg ) {
$messages .= "\n\n" . $msg;
}
// Translators: Item Number of payment triggering error.
wp_mail( get_option( 'mcs_to' ), __( 'Payment Conditions Error', 'my-calendar-pro' ), __( 'There were errors processing this payment:', 'my-calendar-pro' ) . $messages );
// Send HTTP code 200 to stop PayPal from continuing to send this IPN query.
status_header( 200 );
die;
}
} else {
wp_die( 'No valid IPN request made' );
}
if ( '200' === $response_code ) {
if ( 'VERIFIED' === $response ) {
$status = '';
// See whether the transaction already exists. (For refunds, reversals, or canceled reversals).
switch ( $payment_status ) {
case 'Completed':
case 'Created':
case 'Denied':
case 'Expired':
case 'Failed':
case 'Processed':
case 'Voided':
$status = $payment_status;
break;
case 'Pending':
$status = $payment_status . ': ' . sanitize_text_field( $_POST['pending_reason'] );
break;
case 'Refunded':
case 'Reversed':
case 'Canceled_Reversal':
$status = $payment_status . ': ' . sanitize_text_field( $_POST['ReasonCode'] );
break;
}
$values = array(
'item_number' => $item_number,
'txn_id' => $txn_id,
'price' => $price,
'mc_fee' => $mc_fee,
'payer_first' => $payer_first,
'payer_last' => $payer_last,
'payer_email' => $payer_email,
);
mcs_process_payment( $values, $status );
} else {
// log for manual investigation.
$blog = get_option( 'blogname' );
$from = "From: $blog Events <" . get_option( 'mcs_from' ) . '>';
$subject = __( 'INVALID IPN on My Calendar Pro Payment', 'my-calendar-pro' );
$body = __( 'Something went wrong. Hopefully this information will help:', 'my-calendar-pro' ) . "\n\n";
foreach ( $_POST as $key => $value ) {
$body .= $key . ' = ' . $value . "\n";
}
wp_mail( get_option( 'mcs_to' ), $subject, $body, $from );
}
} else {
$blog = get_option( 'blogname' );
$from = "From: $blog Events <" . get_option( 'mcs_from' ) . '>';
$subject = __( 'WordPress failed to contact PayPal', 'my-calendar-pro' );
$body = __( 'Something went wrong. Hopefully this information will help:', 'my-calendar-pro' ) . "\n\n";
$body .= print_r( $ipn, 1 );
wp_mail( get_option( 'mcs_to' ), $subject, $body, $from );
}
status_header( 200 );
}
add_action( 'mcs_process_payment', 'mcs_paypal_processing' );
/**
* Generate payment form
*
* @param float $price Price.
* @param string $currency Currency ID.
* @param float $discount_rate Discount percentage.
* @param array $discounts Discount info.
* @param bool $discount Is discount active.
*
* @return string
*/
function mcs_paypal_insert_form( $price, $currency, $discount_rate, $discounts, $discount ) {
if ( ! get_option( 'mcs_paypal_merchant_id' ) && current_user_can( 'manage_options' ) ) {
$form = '<p class="warning"><strong>' . __( 'PayPal account is not configured.', 'my-calendar-pro' ) . '</strong></p>';
} else {
$use_sandbox = get_option( 'mcs_use_sandbox' );
$form = "
<form action='" . ( 'true' !== $use_sandbox ? 'https://www.paypal.com/cgi-bin/webscr' : 'https://www.sandbox.paypal.com/cgi-bin/webscr' ) . "' method='POST'>
<input type='hidden' name='cmd' value='_xclick' />
<input type='hidden' name='business' value='" . esc_attr( get_option( 'mcs_paypal_merchant_id' ) ) . "' />";
// Translators: Site name.
$form .= "<input type='hidden' name='item_name' value='" . esc_attr( sprintf( __( '%s Event Key Purchase', 'my-calendar-pro' ), get_option( 'blogname' ) ) ) . "' />
<input type='hidden' name='item_number' value='1' />
<input type='hidden' name='amount' value='" . esc_attr( $price ) . "' />
<input type='hidden' name='no_shipping' value='1' />
<input type='hidden' name='no_note' value='1' />
<input type='hidden' name='currency_code' value='" . esc_attr( $currency ) . "' />";
$form .= "
<input type='hidden' name='notify_url' value='" . esc_url( add_query_arg( 'mcsipn', 'true', home_url() ) ) . "' />
<input type='hidden' name='return' value='" . esc_url( add_query_arg( 'response_code', 'thanks', mc_get_current_url() ) ) . "' />
<input type='hidden' name='cancel_return' value='" . esc_url( add_query_arg( 'response_code', 'cancel', mc_get_current_url() ) ) . "' />";
if ( true === $discount && $discount_rate > 0 ) {
$form .= "<input type='hidden' name='discount_rate' value='" . esc_attr( $discount_rate ) . "' />";
}
// API requires a button with the class 'mcs-change-quantity' to toggle in and out of payment form.
// Translators: Amount owed.
$form .= "<button type='submit' name='submit' class='button mcs-button'>" . sprintf( __( 'Pay - %s', 'my-calendar-pro' ), mcs_money_format( $price ) ) . "</button> <button type='button' class='mcs-change-quantity'>" . __( 'Change Quantity', 'my-calendar-pro' ) . '</button>';
/**
* Insert additional HTML after PayPal payment button.
*
* @hook mcs_paypal_form
*
* @param {string} $form Additional HTMl inserted after inputs, inside form.
* @param {float} $price Amount to submit to PayPal.
* @param {string} $currency Currency tag.
* @param {float} $discount_rate Any applied discount rate.
* @param {array} $discounts Information about available discounts.
* @param {bool} $discount Whether discount is active.
*
* @return {string}
*/
$form .= apply_filters( 'mcs_paypal_form', '', $price, $currency, $discount_rate, $discounts, $discount );
$form .= '</form>';
}
return $form;
}