I'm using this plugin in a CakePHP Application. Everything seems to work except sending emails.
I have the following code in AppController.php
function afterPaypalNotification($txnId)
{
//Here is where you can implement code to apply the transaction to your app.
//for example, you could now mark an order as paid, a subscription, or give the user premium access.
//retrieve the transaction using the txnId passed and apply whatever logic your site needs.
$transaction = ClassRegistry::init('PaypalIpn.InstantPaymentNotification')->findById($txnId);
$this->log($transaction['InstantPaymentNotification']['id'], 'paypal');
//Tip: be sure to check the payment_status is complete because failure
// are also saved to your database for review.
if ($transaction['InstantPaymentNotification']['payment_status'] == 'Completed')
{
//Yay! We have monies!
ClassRegistry::init('PaypalIpn.InstantPaymentNotification')->email(array(
'id' => $txnId,
'subject' => 'Thanks!',
'message' => 'Thank you for the transaction!'
));
}
else
{
//Oh no, better look at this transaction to determine what to do; like email a decline letter.
ClassRegistry::init('PaypalIpn.InstantPaymentNotification')->email(array(
'id' => $txnId,
'subject' => 'Failed!',
'message' => 'Please review your transaction'
));
}
}
But the data returned from Paypal is saved in the instant_payment_notifications table but for some reason the emails are not sent. Has anybody tried this plugin before and did the email fonctionality work?
Do I need to enable email.php in app/Config for the emails to work? I read somewhere on Cake's website that I don't need that file for emails to work, so I guess that's not where the problem is.
Any help will be appreciated.
Thanks.
In CakePhp 2.5 you should use CakeEmail
Create the file /app/Config/email.php with the class EmailConfig. The /app/Config/email.php.default has an example of this file.
Add following line in /app/Controller/AppController.php before class declaration
App::uses('CakeEmail', 'Network/Email');
In afterPaypalNotification function replace
ClassRegistry::init('PaypalIpn.InstantPaymentNotification')->email(array(
'id' => $txnId,
'subject' => 'Thanks!',
'message' => 'Thank you for the transaction!'
));
with (fast e-mail, no style)
CakeEmail::deliver('customer#example.com', 'Thanks!', 'Thank you for the transaction!', array('from' => 'you#example.com'));
or
$Email = new CakeEmail();
$Email->template('email_template', 'email_layout')
->emailFormat('html')
->to('customer#example.com')
->from('you#domain.com')
->viewVars(array('id' => $txnId))
->send();
Email template goes to /app/View/Emails/html/email_template.ctp
Email layouts goes to /app/View/Layouts/Emails/html/email_layout.ctp
Related
I tried to make the payment online for my site, I work with stripe, the payment is done with success, but I add CardErrorException to handle special error messages, when I put the code 4000 0000 0000 0069 to handle the expired_card exception it normal pass without handling the "You card has expired" exception.
CheckoutController.php
public function store(Request $request)
{
$contents = Cart::content()->map(function ($item) {
return $item->model->name.', '.$item->qty;
})->values()->toJson();
try {
// Enter Your Stripe Secret
\Stripe\Stripe::setApiKey('sk_test_PcRh9XreG5jbXyhCchJf9NCK00dku1xYGi');
$payment_intent = \Stripe\PaymentIntent::create([
'amount' => round(Cart::total() / 100),
'currency' => 'MAD',
'description' => 'Stripe Test Payment ddd',
'receipt_email' => $request->email,
'payment_method_types' => ['card'],
'metadata' => [
'content' => $contents,
'quantity' => Cart::instance('default')->count(),
]
]);
$intent = $payment_intent->client_secret;
Cart::instance('default')->destroy();
return redirect()->route('confirmation.index')->with('success_message', 'Thank you! Your payment has been successfully accepted!');
} catch (CardErrorException $e) {
return back()->withErrors('Error! ' . $e->getMessage());
}
}
Your code is successfully creating a Payment Intent, but you're not confirming it. Payment is not attempted until the Payment Intent gets confirmed.
The special test card number you're using that returns an expired_card decline does not trigger until payment is attempted. That's why this code is not working as expected.
You likely want to add 'confirm' => 'true' to your arguments (assuming you want to confirm the Payment Intent server-side) or use Stripe.js to confirm the Payment Intent client-side (recommended).
Hey guys does anyone knows the way around to add an SMS content in klaviyo from this package in laravel.
Basically I've added this piece of code where I can add the profile and see that green check next to user's email but for SMS it don't appear there. After reading some similar issues faced by others, I found that we need a subscribe endpoint of API to add consent of SMS, But I still can't find a way to do it with this package. Any help and suggestions would be appreciated.
use Klaviyo\Klaviyo as Klaviyo;
use Klaviyo\Model\EventModel as KlaviyoEvent;
use Klaviyo\Model\ProfileModel as KlaviyoProfile;
class KlaviyoformsController extends Controller
{
public function index()
{
$client = new Klaviyo('Your_private_key', 'public key');
$event =
new KlaviyoEvent(
array(
'event' => 'Lead',
'customer_properties' => array(
'$email' => "someone#mailinator9.com",
'$consent' => ['sms', 'email'],
'sms_consent' => true,
'email_consent' => true,
'$first_name' => "Thomas9",
'$last_name' => "Jefferson",
'$phone_number' => "1234567890"
),
'properties' => array()
)
);
$client->publicAPI->track( $event, true );
return view('klaviyoform::dashboard.index');
}
}
```
You must have a capture form for the consent submission within Laravel that will then subscribe them with the appropriate consent data. You must explicitly ask for both email and sms consent.
$client = new Klaviyo('Your_private_key', 'public key');
$customer_properties = [
'$email' => "someone#mailinator9.com",
'$first_name' => "Thomas9",
'$last_name' => "Jefferson",
'phone_number' => "1234567890"
];
$consents = [];
if (request()->get('sms_consent', false)) {
$consents['sms_consent'] = true;
}
if (request()->get('email_consent', false)) {
$consents['email_consent'] = true;
}
foreach ($consents as $type => $consented) {
if ($consented) {
$consents['$consent'] = array_merge(
data_get($consents, '$consent', []),
[explode('_', $type)[0]] //e.g. sms, email
);
}
}
$client->lists->addSubscribersToList('ListId', array_merge($customer_properties, $consents));
This is relative psuedo code but the goal is clear: Determine if they allowed both sms and email consent. If so, we'll add their consent properties.
Once the consent data has been added to that list you can safely send them sms and/or email based on the customers choice.
I am trying to use Stripe Connect on my website. I created connected account and customers but have a mistake while trying to share a customer to the connected account.
I get this :
"You provided a customer without specifying a source. The default source of the customer is a source and cannot be shared from existing customers."
Hier is the code I am using :
function addSource($source){
$this->source = $source;
}
function addCustomer(){
$customer = \Stripe\Customer::create(array(
"description" => "Customer ".$this->getCustomerName(),
"email" => $this->getCustomerEmail(),
"source" => $this->source
));
$this->customer = $customer;
}
function createAccount(){
$account = \Stripe\Account::create(array(
"country" => "FR",
"type" => "custom"
));
$this->account = $account->id;
}
function connectCustomer(){
$token = \Stripe\Token::create(array(
"customer" => $this->customer->id
), array("stripe_account" => $this->account));
$copiedCustomer = \Stripe\Customer::create(array(
"description" => "Customer for xxx#xxx.com",
"source" => $token->id
), array("stripe_account" => $this->account));
$this->copiedCustomer = $copiedCustomer;
}
By debugging I saw that the problem happend when I try to create $token in the connectCustomer function. The customer is well added on my Stripe Dashboard with a correct source. The account is also created.
My goal after that is to subscribe the customer to the connected account. I have succeed to subscribe him without using Stripe Connect but now I need to use it.
I tried to find a solution in many other forum but did not find anything similar.
Thanks for any help !
I know it's already too late, but here is something worked for me. I have had the same error. I have solved it by creating source instead of token.
So, replace
$token = \Stripe\Token::create(array(
"customer" => $this->customer->id
), array("stripe_account" => $this->account));
with
$token = \Stripe\Source::create([
"customer" => $this->customer->id,
], ["stripe_account" => $this->account]);
Your code looks fine to me. I'm not sure why it did not work. Maybe some steps are missing? Ran into the same issue while creating connect subscription because I was trying to associate platform customer with connected plan. The error message was not very helpful. I search google with the message and came upon this question. C'mon, I was able to use the platform customer to create one-time connected charge fine (after I create a shared source). Why can't I do the same here? Because Subscription API required customer and not a shared source. Stripe demo code wasn't of much help until I carefully read the first 4 bulleted points in the documentation here: https://stripe.com/docs/connect/subscriptions (an Ahhhhah moment!)
Using subscriptions with Connect has these restrictions:
Both the customer and the plan must be created on the connected
account (not your platform account)
Subscriptions must be created directly on the connected account (using the destination parameter is not supported)
Your platform can’t update or cancel a subscription it did not create
Your platform can’t add an application_fee to an invoice that it didn’t create or that contains invoice items it didn’t create
So, I'm just going to post some pseudo code in hoping it will help the next person who came upon this question with above error message.
You have to create a source (not token) to be reuse from the front-end/client-side javascript:
stripe.createSource(card, ownerInfo)
Then you would use this source (stripe_token) to create a customer on the platform account (stripe_customer_id). This can be useful with one-time connected charge (if you have a need for it). This is also to store the original source (stripe_token) so you can create a new re-usable token/source later for the connected/txn_customer_id.
From step 3 on, all the codes are inside of chargeMonthly function below:
Make sure the subscription plan is a connected plan created by the platform by providing a unique name (3rd bullet point above).
Next, create a new re-useable source with the platform/stripe_customer_id.
Use the new re-usable source to create a customer (txn_customer_id) on the connected account.
Create your subscription with the connected/txn_customer_id and connected plan_id.
public function createNewCustomer(&$input, $forConnect = false) {
try {
// Create new stripe customer
if ($forConnect) {
$cu = \Stripe\Customer::create(array(
'email' => $input['email'],
'source' => $input['connect_token']
), array("stripe_account" => $input['txn_account_id']));
$input['txn_customer_id'] = $cu->id;
}
else {
$cu = \Stripe\Customer::create(array(
'email' => $input['email'],
'source' => $input['stripe_token']
));
$input['stripe_customer_id'] = $cu->id;
$input['txn_customer_id'] = $cu->id;
}
} catch (\Stripe\Error\Base $e1) {
// log error
\Log::error($e1);
return false;
} catch(\Stripe\Error\Card $e2) {
\Log::error($e2);
return false;
} catch (Exception $e) {
\Log::error($e);
return false;
}
return true;
}
public function chargeMonthly(&$input, $qty = 1) {
$plan_name = 'yourplatformname-' . $input['amount'] .'-' . $input['banner_slug'];
// attempt to retrieve monthly plan
// if not found, create new plan
try {
$plan = \Stripe\Plan::retrieve($plan_name, array("stripe_account" => $input['txn_account_id']));
} catch(\Stripe\Error\InvalidRequest $e1) {
// ignore error
// \Log::error($e1);
} catch(Exception $e) {
// ignore error
// \Log::error($e);
}
try {
// create new if not found
if(empty($plan)) {
$plan = \Stripe\Plan::create(array(
'amount' => $input['amount'],
'interval' => 'month',
'currency' => 'usd',
'id' => $plan_name,
"product" => array(
"name" => $plan_name
)
), array("stripe_account" => $input['txn_account_id']));
}
$token = \Stripe\Source::create(array(
'customer' => $input['stripe_customer_id'],
'usage' => 'reusable'
), array("stripe_account" => $input['txn_account_id']));
$input['connect_token'] = $token->id;
$this->createNewCustomer($input, true);
$sub = \Stripe\Subscription::create(array(
'customer' => $input['txn_customer_id'],
'plan' => $plan->id,
'quantity' => $qty,
'application_fee_percent' => $input['application_fee_percent']),
array('stripe_account' => $input['txn_account_id'])
);
$input['txn_id'] = $sub->id;
$input['txn_log'] = json_encode($sub);
$input['recurrence_name'] = $plan->id;
// success
return true;
} catch(\Stripe\Error\InvalidRequest $e1) {
// ignore error
\Log::error($e1);
return false;
} catch(Exception $e) {
\Log::error($e);
return false;
}
}
In CakePHP 2.X, for one of my controller actions, I am trying to send an email to all user's every time a new record is created.
Updated with drmonkeyninja's suggestions:
// NewslettersContoller.php
public function add() {
if($this->request->is('post')) {
$this->Newsletter->create();
if($this->Newsletter->save($this->request->data)) {
$this->Session->setFlash(__('Your newsletter has been submited.'));
$this->redirect(array('action' => 'view', $this->Newsletter->id));
}
else {
$this->Session->setFlash(__('Unable to add your post.'));
return $this->redirect(array('action' => 'add'));
}
}
}
// Newsletter.php
public function afterSave($created, $options = []) {
parent::afterSave($created, $options);
if ($created === true) {
$newsletter = $this->findById($this->id);
// Get all users with an email address.
$emails = ClassRegistry::init('User')->find(
'list',
array(
'fields' => array(
'User.id',
'User.email'
),
'conditions' => array(
'User.email <>' => null
)
)
);
$Email = new CakeEmail('gmail');
$Email->from(array('me#example.com' => 'Caprock Portal'));
$Email->to($emails);
$Email->subject($newsletter['Newsletter']['title']);
$Email->message($newsletter['Newsletter']['content']);
try {
$Email->send();
} catch (Exception $exception) {
$this->log($exception);
}
}
}
As you can see from the code snippet, I use CakeEmail to send every user an email containing the newsletter created in the action. Unfortunately for some reason, every time CakeEmail finishes sending the email, CakePHP ignores my redirect request and proceeds to render a blank view (route is still add). I have verified that it is the CakeEmail function by commenting it and verifying that redirection starts to work again. Nothing is caught in the try-catch block either.
One of my assumptions for the cause of this problem is that the email headers are interfering with the redirect headers for my action. I inspected the network requests through my browser, but nothing seems to be sent out, except from the original post request to the add function.
It would be better to send the email from the Newsletter model in the afterSave() callback:-
// app/Model/Newsletter.php
App::uses('CakeEmail', 'Network/Email');
public function afterSave($created, $options = []) {
parent::afterSave($created, $options);
if ($created === true) {
$newsletter = $this->findById($this->id);
// Get all users with an email address.
$emails = ClassRegistry::init('User')->find(
'list',
array(
'fields' => array(
'User.id',
'User.email'
),
'conditions' => array(
'User.email <>' => null
)
)
);
$Email = new CakeEmail('gmail');
$Email->from(array('me#example.com' => 'Caprock Portal'));
$Email->to($emails);
$Email->subject($newsletter['Newsletter']['title']);
$Email->message($newsletter['Newsletter']['content']);
try {
$Email->send();
} catch (Exception $exception) {
$this->log($exception);
}
}
}
Your controller action would then just be:-
// app/Controller/NewslettersController.php
public function add() {
if ($this->request->is('post')) {
$this->Newsletter->create();
if ($this->Newsletter->save($this->request->data)) {
return $this->redirect(array('action' => 'view', $this->Newsletter->id));
} else {
$this->Session->setFlash(__('Unable to add your post.'));
return $this->redirect(array('action' => 'add'));
}
}
}
If the view is still not rendering then temporarily disable the afterSave() callback to check that the Controller part of the code is working as expected when saving your newsletter.
Note that you can filter out the users without an email address when you retrieve the users from the database. By using find('list') you don't need to mess with a foreach loop.
You also don't need to use $this->Newsletter->getLastInsertId(); as $this->Newsletter->id should have already been set to this at the time of save.
Cake's debug() method is better to use when debugging variables than var_dump() which incidentally won't work in your example as your are redirecting after it has been output!
You can also try specifying the controller in the url array:
if($Email->send()) {
return $this->redirect(
array('controller' => 'controller_name', 'action' => 'view', $id)
);
} else {
return $this->redirect(
array('controller' => 'controller_name', 'action' => 'add')
);
}
Remove the var_dump. It breaks HTTP protocol if you try to send header information (for the redirect) after content has already been sent.
Another alternative is to send the emails out as a Shell/Console using a Cronjob.
This is a link to the book where you can find a bunch of information about creating shells.
CakePHP 3.x
http://book.cakephp.org/3.0/en/console-and-shells.html
CakePHP 2.x
http://book.cakephp.org/2.0/en/console-and-shells.html
This eliminates blocking your processing. If you send emails in controller or model, your app is still waiting for the emails to be sent before the page is rendered or redirected. So by moving to a cronjob the user has as faster better experience because the are not waiting for emails to be sent out.
So how do you know what to send out via the shell??? Well that depends on your use case but I flag records that need sent with a database column for this purpose, find all the ones that haven't been sent, mark those found records with another flag as being processed, the second flag in the database prevents two cron tasks from reading and sending the same records while the first cronjob is still processing. I like to set my cronjobs for mailing as low as possible for transaction type emails, however that can depend on the hosting provider and their settings.
I was wondering if someone could help me with a problem I have been having some trouble researching related to Laravel and inbound email processing through Mandrill.
Basically I wish to be able to receive emails through Mandrill and store them within my Laravel database. Now i'm not sure if i'm reading through the documentation with the wrong kind of eyes, but Mandrill says it deals with inbound email as well as outbound, however i'm starting to think that Mandrill deals with inbound email details as opposed to the actual inbound email, such as if the message is sent etc.
I've created a new Mandrill account, created an API key, created an inbound domain and corresponding subdomain of my site (e.g. inboundmail.myproject.co.uk), set the MX record and the MX record is showing as valid. From there i have set up a route (e.g. queries#inboundmail.myproject.co.uk), and a corresponding webhook (myproject.co.uk/inboundmail.php) and within this webhook tried a variety of the examples given in the API (https://mandrillapp.com/api/docs/inbound.php.html), such as adding a new route, checking the route and attempting to add a new domain. All of them worked and produced the correct results, so my authentication with Mandrill is not in question, but my real question is is there a specific webhook for dealing with accepting incoming mail messages?
I cant help but feel like an absolute idiot asking this question as i'm sure the answer is either staring me in the face or just not possible through Mandrill.
Thanks in advance.
Thanks to duellsy and debest for their help, in the end i found a script and expanded on it to add the mail to my own database and style / display it accordingly. Hope this helps someone who may have the same trouble:
<?php
require 'mandrill.php';
define('API_KEY', 'Your API Key');
define('TO_EMAIL', 'user#example.com');
define('TO_NAME', 'Foo Bar');
if(!isset($_POST['mandrill_events'])) {
echo 'A mandrill error occurred: Invalid mandrill_events';
exit;
}
$mail = array_pop(json_decode($_POST['mandrill_events']));
$attachments = array();
foreach ($mail->msg->attachments as $attachment) {
$attachments[] = array(
'type' => $attachment->type,
'name' => $attachment->name,
'content' => $attachment->content,
);
}
$headers = array();
// Support only Reply-to header
if(isset($mail->msg->headers->{'Reply-to'})) {
$headers[] = array('Reply-to' => $mail->msg->headers->{'Reply-to'});
}
try {
$mandrill = new Mandrill(API_KEY);
$message = array(
'html' => $mail->msg->html,
'text' => $mail->msg->text,
'subject' => $mail->msg->subject,
'from_email' => $mail->msg->from_email,
'from_name' => $mail->msg->from_name,
'to' => array(
array(
'email' => TO_EMAIL,
'name' => TO_NAME,
)
),
'attachments' => $attachments,
'headers' => $headers,
);
$async = false;
$result = $mandrill->messages->send($message, $async);
print_r($result);
} catch(Mandrill_Error $e) {
// Mandrill errors are thrown as exceptions
echo 'A mandrill error occurred: ' . get_class($e) . ' - ' . $e->getMessage();
// A mandrill error occurred: Mandrill_PaymentRequired - This feature is only available for accounts with a positive balance.
throw $e;
}
?>
Like using webhooks from other mail parsing services, you'll need to make use of
file_get_contents("php://input")
This will give you the raw data from the webhook, which you can then json_decode and work with the results.