I'm trying to create a payment with Omnipay and Mollie in my Laravel project. I'm using the following 2 libraries:
https://github.com/barryvdh/laravel-omnipay
https://github.com/thephpleague/omnipay-mollie
I'm doing the following in my code:
$gateway = Omnipay\Omnipay::create('Mollie');
$gateway->setApiKey('test_gSDS4xNA96AfNmmdwB3fAA47zS84KN');
$params = [
'amount' => $ticket_order['order_total'] + $ticket_order['organiser_booking_fee'],
'description' => 'Bestelling voor klant: ' . $request->get('order_email'),
'returnUrl' => URL::action('EventCheckoutController#fallback'),
];
$response = $gateway->purchase($params)->send();
if ($response->isSuccessful()) {
session()->push('ticket_order_' . $event_id . '.transaction_id',
$response->getTransactionReference());
return $this->completeOrder($event_id);
}
The payment works. When the payment is done he goes back to the function fallback. But I don't know what to put in this function and how to go back to the line if($response->isSuccesfull()...).
The most important thing I need to do after the payment is :
session()->push('ticket_order_' . $event_id . '.transaction_id',
$response->getTransactionReference());
return $this->completeOrder($event_id);
Can someone help me figure out how to work with the fallback function and above?
A typical setup using Mollie consists of three separate pages:
a page to create the payment;
a page where Mollie posts the final payment state to in the background; and
a page where the consumer returns to after the payment.
The full flow is described in the Mollie docs. Also take a look at the flow diagram at that page.
DISCLAIMER: I've never used Omnipay myself and did not test the following code, but it should at least give you an idea how to set up your project.
Creating the payment:
$gateway = Omnipay\Omnipay::create('Mollie');
$gateway->setApiKey('test_gSDS4xNA96AfNmmdwB3fAA47zS84KN');
$params = [
'amount' => $ticket_order['order_total'] + $ticket_order['organiser_booking_fee'],
'description' => 'Bestelling voor klant: ' . $request->get('order_email'),
'notifyUrl' => '', // URL to the second script
'returnUrl' => '', // URL to the third script
];
$response = $gateway->purchase($params)->send();
if ($response->isRedirect()) {
// Store the Mollie transaction ID in your local database
store_in_database($response->getTransactionReference());
// Redirect to the Mollie payment screen
$response->redirect();
} else {
// Payment failed: display message to the customer
echo $response->getMessage();
}
Receiving the webhook:
$gateway = Omnipay\Omnipay::create('Mollie');
$gateway->setApiKey('test_gSDS4xNA96AfNmmdwB3fAA47zS84KN');
$params = [
'transactionReference' => $_POST['id'],
];
$response = $gateway->fetchTransaction($params);
if ($response->isPaid()) {
// Store in your local database that the transaction was paid successfully
} elseif ($response->isCancelled() || $response->isExpired()) {
// Store in your local database that the transaction has failed
}
Page where the consumer returns to:
// Check the payment status of your order in your database. If the payment was paid
// successfully, you can display an 'OK' message. If the payment has failed, you
// can show a 'try again' screen.
// Most of the time the webhook will be called before the consumer is returned. For
// some payment methods however the payment state is not known immediately. In
// these cases you can just show a 'payment is pending' screen.
Related
i have integrated coinbase api in my web app. When charge is created, users are directed to coinbase commerce website to make payment. How do i check if the user has finished paying or not and if the exact amount has been paid. Below is my code
<?php
require_once __DIR__ . "/vendor/autoload.php";
use CoinbaseCommerce\ApiClient;
use CoinbaseCommerce\Resources\Charge;
/**
* Init ApiClient with your Api Key
* Your Api Keys are available in the Coinbase Commerce Dashboard.
* Make sure you don't store your API Key in your source code!
*/
ApiClient::init("MY API KEY HERE");
$chargeObj = new Charge();
$chargeObj->name = 'Bitcoin Deposit';
$chargeObj->description = 'Testing the payment system';
$chargeObj->local_price = [
'amount' => '100.00',
'currency' => 'USD'
];
$chargeObj->pricing_type = 'fixed_price';
try {
$chargeObj->save();
// insert into database with status pending
$queryobject->insertTransaction($_SESSION['user_id'],$chargeObj->id, $amount, $status, $currentTime,
$chargeObj->name, $chargeObj->currency);
} catch (\Exception $exception) {
echo sprintf("Sorry! payment could not be created. Error: %s \n", $exception->getMessage());
}
if ($chargeObj->id) {
$chargeObj->description = "New description";
// Retrieve charge by "id"
try {
$retrievedCharge = Charge::retrieve($chargeObj->id);
$hosted_url = $retrievedCharge->hosted_url;
header('location: '.$hosted_url);
} catch (\Exception $exception) {
echo sprintf("Enable to retrieve charge. Error: %s \n", $exception->getMessage());
}
}
You need to use webhooks for this, you can create an endpoint for this. Unfortunately Coinbase does not have a sandbox environment so you are going to need a bit of cryptocurrency in your account.
So I'm building an webapp that has a shop with Laravel API and Vue as the frontend SPA.
I've been trying to use Strip to enable payments. So far, with the help of Stripe's documentation, I have been able to create a Source in the frontend. for iDEAL, Stripe highly suggests us to make use of webhooks to confirm whether a payment has succeeded. (I'm using Spatie/Laravel-Stripe-Webhook package) This is the current flow of my webapp:
Checkout.vue:
checkout() {
const sourceData = {
type: 'ideal',
amount: this.cart.total,
currency: 'eur',
owner: {
name: this.name + ' ' + this.last_name,
email: this.email,
},
metadata: {
order: JSON.stringify(order),
total_quantity: this.cart.total_quantity,
},
redirect: {
return_url: 'http://example.test/order-confirmation',
},
}
this.stripe.createSource(this.ideal, sourceData).then(function(result) {
if (result.error) {
console.log(error.message)
this.error = error.message
} else {
stripeSourceHandler(result.source)
}
})
const stripeSourceHandler = source => {
document.location.href = source.redirect.url
}
},
After filling in billing address, emails etc. the user starts the payment.
User gets redirected to iDEAL payment page where they can authorize payment.
The Source is now created. Stripe sends source.chargeable webhook:
config/stripe-webhooks.php:
'jobs' => [
'source_chargeable' => \App\Jobs\StripeWebhooks\ProcessPaymentsJob::class,
'charge_succeeded' => \App\Jobs\StripeWebhooks\ChargeSucceededJob::class,
],
ProcessPaymentsJob.php:
public function __construct(WebhookCall $webhookCall)
{
$this->webhookCall = $webhookCall;
}
public function handle()
{
$charge = $this->webhookCall->payload['data']['object'];
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
$user = '';
if(User::find(Auth::id())) {
$user = $user->name;
} else {
$user = 'a guest';
}
$payment = \Stripe\Charge::create([
'amount' => $charge['amount'],
'currency' => 'eur',
'source' => $charge['id'],
'description' => 'New payment from '. $user,
'metadata' => [
'order' => $charge['metadata']['order'],
'total_quantity' => $charge['metadata']['total_quantity'],
]
]);
}
User returns to redirect[return_url]
If all went well, Stripe should send charge.succeeded webhook:
ChargeSucceededJob.php:
public function __construct(WebhookCall $webhookCall)
{
$this->webhookCall = $webhookCall;
}
public function handle()
{
$charge = $this->webhookCall->payload['data']['object'];
$order = Order::create([
'user_id' => Auth::id() ?? null,
'payment_id' => $charge['id'],
'payment_method' => $charge['payment_method_details']['type'],
'billing_email' => $charge['billing_details']['email'],
'billing_name' => $charge['metadata']['name'],
'billing_last_name' => $charge['metadata']['last_name'],
'billing_address' => $charge['metadata']['address'],
'billing_address_number' => $charge['metadata']['address_num'],
'billing_postal_code' => $charge['metadata']['postal_code'],
'billing_city' => $charge['metadata']['city'],
'billing_phone' => strval($charge['billing_details']['phone']),
'order' => json_decode($charge['metadata']['order']),
'total_quantity' => (int) $charge['metadata']['total_quantity'],
'billing_total' => $charge['amount'],
]);
}
This is all going well. However, I do not know how to notify the customer (on the frontend) that the order has been completed. In Stripe's documentation, they explain how to retrieve the Source on the order confirmation page, but they do not explain how to retrieve the Charge, because this is what determines whether the whole order has been completed or not.
OrderConfirmation.vue:
checkPaymentStatus() {
this.stripe = Stripe(this.stripeKey)
// After some amount of time, we should stop trying to resolve the order synchronously:
const MAX_POLL_COUNT = 10;
let pollCount = 0;
let params = new URLSearchParams(location.search)
const pollForSourceStatus = async () => {
const { source } = await this.stripe.retrieveSource({id: params.get('source'), client_secret: params.get('client_secret')})
if (source.status === 'chargeable') {
// Make a request to your server to charge the Source.
// Depending on the Charge status, show your customer the relevant message.
} else if (source.status === 'pending' && pollCount < MAX_POLL_COUNT) {
// Try again in a second, if the Source is still `pending`:
pollCount += 1;
setTimeout(pollForSourceStatus, 1000);
} else {
// Depending on the Source status, show your customer the relevant message.
}
};
pollForSourceStatus();
}
How do I go from here? I am trying to notify the frontend when the Charge has been succeeded. My initial thought process was just to return the Order object, as I would do if it was a Controller, but if I understand correctly, the Job is running asynchronously, so I can't return data. I am also new to Jobs and Queues and stuff, I'm still trying to wrap my head around with it.
Another option I thought of is that I would poll requests from the frontend to the backend to request the last Order, but I have no idea how this would work and/or if this is a good solution.
Any help/tips/helpful resources would be much appreciated!
iDEAL payments are asynchronous, but they luckily do immediately notify you if the payment was successful or not.
When the iDEAL process is complete and your user is redirected to your site, Stripe automatically appends some query parameters to the URL. Meaning your users will be redirected to something like:
https://example.com/checkout/complete?payment_intent=pi_123&payment_intent_client_secret=pi_123_secret_456&source_type=ideal
The next step is to then retrieve the PaymentIntent and check on its status, which you can do by either:
Retrieving the PaymentIntent on the client using stripe.js and the PaymentIntent client secret: https://stripe.com/docs/js/payment_intents/retrieve_payment_intent
Retrieving the PaymentIntent on the server by sending an ajax request to your backend with the PaymentIntend ID: https://stripe.com/docs/api/payment_intents/retrieve
If the status is succeeded, then the payment was completed and you can proceed from there.
The response i keep getting at dd($finalResponse); is:
RestResponse {#298 ▼
#statusCode: 400
#request: RestCompletePurchaseRequest {#300 ▶}
#data: array:4 [▼
"name" => "PAYMENT_NOT_APPROVED_FOR_EXECUTION"
"message" => "Payer has not approved payment"
"information_link" => "https://developer.paypal.com/webapps/developer/docs/api/#PAYMENT_NOT_APPROVED_FOR_EXECUTION"
"debug_id" => "5471589613718"
]
}
Here is the code.
$gateway = Omnipay::create('PayPal_Rest');
// Initialise the gateway
$gateway->initialize(array(
'clientId' => env('PAYMENT_SANDBOX_PAYPAL_CLIENTID'),
'secret' => env('PAYMENT_SANDBOX_PAYPAL_SECRET'),
'testMode' => true, // Or false when you are ready for live transactions
));
// Do an authorisation transaction on the gateway
$transaction = $gateway->authorize(array(
'returnUrl'=> env('PAYMENT_SANDBOX_PAYPAL_URL'),
'cancelUrl' => 'http://localhost:8000/cancel',
'amount' => '10.00',
'currency' => 'AUD',
'description' => 'This is a test authorize transaction.',
// 'card' => $card,
));
$response = $transaction->send();
if ($response->isSuccessful()) {
// Find the authorization ID
$authResponse = $response->getTransactionReference();
echo "Authorize transaction was successful!\n".$authResponse;
}else{
echo "Failed to auth transaction";
dd($response);
}
// Once the transaction has been approved, we need to complete it.
$transaction = $gateway->completePurchase(array(
'payerId' => $request->PayerID,
'transactionReference' => $authResponse
));
$finalResponse = $transaction->send();
dd($finalResponse);
if ($finalResponse->getData()) {
echo "Transaction was successful!\n";
// Find the authorization ID
$results = $finalResponse->getTransactionReference();
dd($results);
}else{
dd($finalResponse->getData());
}
After logging in as the payer and completing purchase, what else does the payer need to approve and how?
No, you aren't understanding the PayPal payment flow correctly. Here is the correct flow:
You do the call to Omnipay::create(), $gateway->initialize() and $gateway->authorize() just like you have them above. However for returnUrl you have to provide a URL on your site, just like you have for cancelUrl. Perhaps you mean to use http://localhost:8000/return (although better would be to have a transaction ID or something in the return URL).
The response from the $gateway->authorize() will be of type RedirectResponse. You can check this:
// Do an authorisation transaction on the gateway
$transaction = $gateway->authorize(array(
'returnUrl'=> env('PAYMENT_SANDBOX_PAYPAL_URL'),
'cancelUrl' => 'http://localhost:8000/cancel',
'amount' => '10.00',
'currency' => 'AUD',
'description' => 'This is a test authorize transaction.',
// 'card' => $card,
));
$response = $transaction->send();
if ($response->isRedirect()) {
// Yes it's a redirect. Redirect the customer to this URL:
$redirectUrl = $response->getRedirectUrl();
}
At that point the initial handshake with the customer is over. You have now redirected the customer to the PayPal web site where they will authorize the transaction by logging in with their PayPal account email address and password, check the invoice, click the button that says that they agree to pay.
The next thing that happens is that the customer is redirected by PayPal back to your web site, on the redirectUrl that you provided in the authorize() call. That will jump to a different place in your code. At that point you call completeAuthorize, just like you had in your code earlier:
// Once the transaction has been approved, we need to complete it.
$transaction = $gateway->completePurchase(array(
'payerId' => $request->PayerID,
'transactionReference' => $authResponse
));
$finalResponse = $transaction->send();
dd($finalResponse);
if ($finalResponse->getData()) {
echo "Transaction was successful!\n";
// Find the authorization ID
$results = $finalResponse->getTransactionReference();
dd($results);
}else{
dd($finalResponse->getData());
}
Note that you need to have stored the Payer ID and transactionReference from the authorize call and be able to recover those in your returnUrl code.
You also need to be able to handle the cancelUrl case, where the customer has decided not to agree to the payment on PayPal and gets sent back to the cancelUrl URL on your website instead.
Finally you need to be able to handle the occasional cases where the customer completes the payment on the PayPal web site but does not end back on your returnUrl. This could be because of a network issue, the browser crashed, or because the customer closed their browser between clicking "Agree to Pay" on PayPal and landing back on your site. The best way to handle those is with the omnipay-paypal calls fetchPurchase() or listPurchase().
I am a newbie to codeigniter and so far I have been doing great in developing my first application. However, when I tried to integrate CI_MERCHANT library to the site I am getting redirected to paypal just fine and I can even complete the transaction successfully and get redirected to my website. However, I am stuck on how to verify the "hidden information" sent by paypal to my application in addition to extracting this information and posting it to the database.
in my controller I have this:
public function place_order($id=NULL){
$this->merchant->load('paypal_express');
$id=$this->session->userdata('id');
$customer_id=$id;
$rules=$this->order_m->rules_place_order;
$this->form_validation->set_rules($rules);
if ($this->form_validation->run() == FALSE) // validation hasn't been passed
{
$this->data['subview']='customer/order_view';
$this->load->view('templates/header_customer');
$this->load->view('customer/_layout_main',$this->data);
$this->load->view('templates/footer_customer');
}
else // passed validation proceed to post success logic
{
// build array for the model
$data=$this->order_m->array_from_order(array('topic_title','discipline','academic_level','type_of_service','paper_format','pages','no_of_sources','no_of_slides','paper_details','deadline','timezones'));
$data['customer_id']=$id;
$this->order_m->save_data($data);
$this->db->where('customer_id',$id);
//get the last inserted id
$no=$this->db->insert_id();
$settings=$this->merchant->default_settings();
//payment for order
$params = array(
'amount' => 100.00,
'currency' => 'USD',
'return_url' => 'http://localhost/customers/order/paypal',
'cancel_url' => 'http://localhost/customers/order'
);
$response=$this->merchant->purchase($params);
}
}
public function paypal(){
var_dump($_GET);
$this->merchant->load('paypal_express');
$settings=$this->merchant->default_settings();
$params = array(
'amount' => 100.00,
'currency' => 'USD',
);
$response=$this->merchant->purchase_return($params);
var_dump($response);
if ($response->status() == Merchant_response::AUTHORIZED)
{
echo "status is AUTHORIZED";
}
if ($response->status() == Merchant_response::FAILED)
{
echo "status is FAILED";
}
if ($response->status() == Merchant_response::REDIRECT)
{
echo "status is REDIRECT";
}
if ($response->status() == Merchant_response::COMPLETE)
{
echo "status is COMPLETE";
}
if ($response->status() == Merchant_response::REFUNDED)
{
echo "status is REFUNDED";
}
This redirects me successfully to paypal and I can complete the transaction.
However, I am unable to proceed from here since I am a newbie to payment processing. Kindly point me in the right direction on how to:
1. Verify every transaction with paypal and be able to visualize and post this information to my database.
2. Compare the information I posted to the database prior to redirecting the customer to paypal with what I receive from paypal.
First check the detailed info about IPN here
When you are creating button in step3 you can specify IPN the url
Key points
You will receive the data at IPN url in $_POST variable
read that $_POST variable with mail('email', 'subject', 'data with $_POST') or writing into the log file
You need to call the purchase_return() method when the customer is sent to your return_url. This will confirm the transaction with PayPal.
I am trying to create a subscription i Paymill. Ive read through their examples but i do not seem to get it.
All i really want to do is to setup a subscription, here is my current code:
$token = $_POST['paymillToken'];
if ($token) {
require "Services/Paymill/Payments.php";
require "Services/Paymill/Transactions.php";
require "Services/Paymill/Subscriptions.php";
require "Services/Paymill/Offers.php";
$params = array(
'amount' => '49900', // Cent!
'currency' => 'SEK', // ISO 4217
'token' => $token,
'description' => 'User ID# ' . $userIdMain . ' Email: ' . $userEmail
);
$transactionsObject = new Services_Paymill_Transactions(
PAYMILL_API_KEY, PAYMILL_API_HOST
);
$transaction = $transactionsObject->create($params);
echo "<br /><br />";
print_r($transaction);
echo $transaction['client']['id'];
echo $transaction['payment']['id'];
$params = array(
'client' => $transaction['client']['id'],
'offer' => 'offer_9cdffb501f565bf827a8',
'payment' => $transaction['payment']['id']
);
$subscriptionsObject = new Services_Paymill_Subscriptions(PAYMILL_API_KEY, PAYMILL_API_HOST);
$subscription = $subscriptionsObject->create($params);
echo "<br /><br />";
print_r($subscription);
}
The problem is that the above create two payments at once. But it seems like the subscription object requires me to first have a payment_id (see $transaction['payment']['id'] above).
What am i doing wrong here?
Creating a subscription will also create a transaction, which is correct. Add a Payment Object ( https://www.paymill.com/en-gb/documentation-3/reference/api-reference/#create-new-credit-card-payment-with ) instead of creating a new transaction and pass the id to the Subscription->create().
OK it's not PHP but I had the same kind of uncertainty following the right steps to set up a subscription. In the end it's kind of easy, so I am posting my code so far in case it helps.
The app is MVC .net and I am using the PayMill PayButton to generate a payment token. This is part of the model during the registration process. The following code calls the .net wrapper of the PayMill API.
private static async Task<Subscription> MakePayMillSubscription(int amount, ApplicationUser user, PaymillContext paymillContext, Payment payment, Client client)
{
SubscriptionService subscriptionService = paymillContext.SubscriptionService;
Subscription subscription = await subscriptionService.CreateAsync( payment, client, null, amount, "EUR", Interval.periodWithChargeDay(1, Interval.TypeUnit.MONTH), null, user.Id, Interval.period(10, Interval.TypeUnit.YEAR));
return subscription;
}
private static async Task<Client> MakePayMillClient(RegisterViewModel model, ApplicationUser user, PaymillContext paymillContext)
{
ClientService clientService = paymillContext.ClientService;
Client client = await clientService.CreateWithEmailAndDescriptionAsync(
model.Email,
user.Id
);
return client;
}
private static async Task<Payment> MakePayMillPayment(RegisterViewModel model,PaymillContext context, Client client)
{
Payment payment = await context.PaymentService.CreateWithTokenAndClientAsync(model.paymillToken, client);
return payment;
}
Then I call these during registration like this at the moment (I am still in a testing phase)
client = await MakePayMillClient(model, user, paymillContext);
payment = await MakePayMillPayment(model, paymillContext,client);
subscription = await MakePayMillSubscription((int)(account.TotalAmountDue * 100.0M), user, paymillContext, payment, client);