Jump to content

Need Help Building A Payment Gateway Module


randvegeta

Recommended Posts

it's very common the gateway only do a redirection, nothing prevent you to use it as the blesta postback/callback ending point for update order status in blesta  and after that you just do the internal blesta redirection for customer get his display page.

 

 

 

For your info, if I was you, I will only create one function to manage the gateway response and will do the check/validation within just using a condition "if" "else"

Link to comment
Share on other sites

I assume you just need the paramters?

 

The return URL would look somthing like this

 

<Blesta.Return_URL>/?invoiceid=111&amount=9.70&currency=USD&hash=HGGJUWGSNDHSD

 

The important part is the 'hash'. I can generate my own hash using the other variables and compare the one returned by CarrotPay. if they match, then the payment is verified.

 

So all I need to do is this:

 

1.) Get Variables

2.) Generate HASH

3.) Compare my hash to CarrotPay hash.

-> if match, then payment is verified

-> else, invalid

 

In terms of paramters in the callback URL, I can include whatever I want actually, so I can add the client ID if needed.

 

I can get the 'GET' paramters and then generate/compare the hash. What I do after that I am not sure on.

 

Link to comment
Share on other sites

From What I can understand, and please correct me if I'm wrong,

 

the 'success' function is the function related to the return_url parameter. It  does nothing but serve as a place for client's to be sent back to after payment is made. Nothing in there saves the transaction details or applies them to invoices.

 

It is instead the 'validate' function which relates to actually processing the payment, but there is no html to be displayed to the client here as it is intended for server to server communication.

 

Is this right?

 

If yes, how am I to proceed given CarrotPay does not use the 'callback' feature, and instead only gives a return URL for the client. The return_url then includes all transaction data to be verified by the merchant.

 

Can I use the callback URL in place of the return URL?

 

Yes, that is correct. And yes, you should use the callback URL in place of the return URL if the gateway doesn't distinguish between the two.

Configure::get("Blesta.gw_callback_url") . Configure::get("Blesta.company_id") ."/carrot_pay/" . $this->ifSet($contact_info['client_id']) . "/"

The customer will still be directed back to the "payment received" page in Blesta.

 

 

 

<Blesta.Return_URL>/?invoiceid=111&amount=9.70&currency=USD&hash=HGGJUWGSNDHSD

 

You do your payment validation logic in CarrotPay::validate, which is called after CarrotPay responds to the callback URL.

public function validate(array $get, array $post) {
    $hash = $this->ifSet($get['hash']);
    $amount = $this->ifSet($get['amount']);

    ...
    // Perform validation logic
    ...

    // Return these fields (your values will vary)
    return array(
        'client_id' => $this->ifSet($get[2]),
        'amount' => $amount,
        'status' => "approved", // ('approved','declined','void','error','pending','refunded', or 'returned')
        'reference_id' => null,
        'transaction_id' => null, 
        'parent_transaction_id' => null,
        'invoices' => array(array('id' => $this->ifSet($get['invoiceid']), 'amount' => $amount)) // An array of arrays, each referencing the ID and Amount of each invoice paid
    );
}

Depending on whether the payment is valid, you return a different status. 'approved' for successful payments, or 'declined', 'void', 'error' if it failed. Choose what is most appropriate based on the response from the gateway. After you return data from CarrotPay::validate, the transaction is complete.

Link to comment
Share on other sites

Okay so I am making some more progress and have managed to log a few test transactions, but it seems as soon as the callback URL is loaded, the page redirects to the 'return_url' page. But there are no parameters passed to it. It just says :

 

 

your payment is being processed

 

How can I debug if I can't see any output? 

Link to comment
Share on other sites

Since I am just testing, is there any way to delete the test transactions?

 

Blesta doesn't provide a method by which to delete transactions from the system. A test environment (separate Blesta install) would be ideal rather than testing on your live installation. Developers with an owned license can request a dev license key for this purpose. However, you could manually delete records from the database from the `transactions` and `transactions_applied` tables.

 

 

How can I debug if I can't see any output? 

 

What are you trying to test? You can kill the script at run time with a call to die. e.g.

print_r($variable);
die; // script ends here
Link to comment
Share on other sites

 

 

What are you trying to test? You can kill the script at run time with a call to die. e.g.

 

Well I just want to see the returned variables are and what hash I generate. Just trying to see how it all goes step by step.

 

I have an owned license, so I suppose I can request a dev license also?

 

Also, I am curious how Blesta handles payments in currencies other than the what is stated in the invoice.

 

For example, as CarrotPay only passes information back via the the return URL, a fraudster may attempt to pass a transaction off by changing some of the parameters of the initial payment.

 

For example, an invoice for US$100 may be issued. The fraudster may change the currency param to Hong Kong Dollars, and pay HK$100 instead. In the return URL, the amount and currency should still correctly show a payment HK$100, and the hash should still be reproduced correctly. But when applying the payment, will the Blesta take into consideration that HK$100 was paid and NOT US$100? If yes, what would happen? If not, how can I do the check myself to make sure the client is paying the correct currency (i.e. how can I check the invoice currency).

Link to comment
Share on other sites

Well I just want to see the returned variables are and what hash I generate. Just trying to see how it all goes step by step.

 

I have an owned license, so I suppose I can request a dev license also?

 

Also, I am curious how Blesta handles payments in currencies other than the what is stated in the invoice.

 

For example, as CarrotPay only passes information back via the the return URL, a fraudster may attempt to pass a transaction off by changing some of the parameters of the initial payment.

 

For example, an invoice for US$100 may be issued. The fraudster may change the currency param to Hong Kong Dollars, and pay HK$100 instead. In the return URL, the amount and currency should still correctly show a payment HK$100, and the hash should still be reproduced correctly. But when applying the payment, will the Blesta take into consideration that HK$100 was paid and NOT US$100? If yes, what would happen? If not, how can I do the check myself to make sure the client is paying the correct currency (i.e. how can I check the invoice currency).

 

If you have an owned license with us, open a ticket to request a dev license and one will be issued.

 

I'm personally not familiar with CarrotPay enough to recommend anything you can do in way of security, Cody or Tyson may be able to. Generally though, if a payment is made in a different currency then it'll be recorded in that currency. Since the invoice currency and the transaction currency do not match, the transaction cannot be applied to the invoice.

 

I've seen orders come in via PayPal and they change the form to say 5 cents and make a payment. Blesta records 5 cents and applies it to the invoice, but the invoice is not closed because it's not paid in full. That's just an example, but Blesta is pretty smart about these kinds of things.

Link to comment
Share on other sites

do carrotpay offer custom parameters you can send, and they will return you intact/unmodified in their answer?

 

because, supposing blesta is requesting the amount in the answser, use in adition an availaible custom parameter from carrotpay to send amount in the "blesta curency", and use this parameter when back as the "blesta amount",

 

because in general custom available parameters from payment gateway are send back same as there was send.

 

so transaction succesfull at carrotpay will be always considered as paid invoice at blesta.

 

 

And only use above solution if carrotpay do not allow partial payment, as by the way using above idea mean you chose to always believe that a successful transaction at carrotpay will be always complete.

Link to comment
Share on other sites

Hi Paul, thanks for your feedback.

 

 

Generally though, if a payment is made in a different currency then it'll be recorded in that currency. Since the invoice currency and the transaction currency do not match, the transaction cannot be applied to the invoice.

 

 

This is good to know. So I need not write any special code to check invoice currency. I can just verify the payment and if the currency is wrong, then it just won't be applied to the invoice then. 

 

I've seen orders come in via PayPal and they change the form to say 5 cents and make a payment. Blesta records 5 cents and applies it to the invoice, but the invoice is not closed because it's not paid in full. That's just an example, but Blesta is pretty smart about these kinds of things.

 

 

That's good. It would be possible for someone to change the payment parameters before making the payment. But for whatever transaction that is actually made, so long as it is recorded correctly, hopefully I need not worry.

 

 

Serge, I'm not sure what you are saying is correct as they contradict with Paul said. Unless of course I am misunderstanding you.

 

do carrotpay offer custom parameters you can send, and they will return you intact/unmodified in their answer?

 

 

I can pass any parameters to CarrotPay, yes. It is highly flexible gateway in this respect.

 

Actually it is done only be adding the paramters to the return URL and it would technically be possible to modify the params by editing the return url itself. However, this should invalidate the hash so it is not a problem.

because, supposing blesta is requesting the amount in the answser, use in adition an availaible custom parameter from carrotpay to send amount in the "blesta curency", and use this parameter when back as the "blesta amount",

 

because in general custom available parameters from payment gateway are send back same as there was send.

 

so transaction succesfull at carrotpay will be always considered as paid invoice at blesta.

 

And only use above solution if carrotpay do not allow partial payment, as by the way using above idea mean you chose to always believe that a successful transaction at carrotpay will be always complete.

 

I really don't understand what you are trying to say here. Sorry.

 

But CarrotPay works like this.

 

You MUST pass the following to CarrotPay: price (including currency) and some 'word' that will be hashed. You can then pass any other parameters within the return URL. 

 

When CarrotPay receives the payment, it does the following:

 

hash($word, $price, $seed);

 

Where:

  price = amount + currency

  seed = secret / password that is stored within the blesta database (CarrotPay knows this secret)

  word = any string you like. Normally with the paramaters you pass in the return URL.

 

So $word is normally a string that looks like $invoices.$amount.$currency.$clientid etc.

 

If someone attempts to modify the parameters in the return URL AFTER payment. the hash will not match and so the transaction would be invalid.

If someone attempts to modify the parameters of the return URL BEFORE payment, then they would also need to change the 'word'. They COULD do that but when the transaction comes through, it will be recognized correctly with the actual details. So if they change the currency or amount, I can check that.

Link to comment
Share on other sites

Okay so my payment gateway module seems to be working. I just need to do some further testing.

 

I have gone through the entire process of ordering and paying an invoice. The payment goes through, and the system logs the payment correctly.

 

However before the invoice is marked as paid, the transaction doesn't immediately get applied to the invoice. After 5 or so minutes, the payment is then applied and the invoice is marked as paid. Not a big deal but is there any reason for this delay? Why is it not applied immediately?

 

Also, even after the payment is made, the product isn't automatically provisioned as is expected. Within the client area it just shows up as a pending product and I need to manually click 'activate' within the admin area. I double checked to make sure there wasn't anything requiring manual approval or intervention. Any ideas?

Link to comment
Share on other sites

great you got it working :-)   sorry if you missunderstanded my bad English as it's not my native language

 

the 5mins to wait come from blesta default cron frequency.

 

to hack blesta cron task frequency if you would, see my hack at the end of post

 

http://www.blesta.com/forums/index.php?/topic/2990-email-not-being-sent-on-invoice-creation/page-2

Link to comment
Share on other sites

However before the invoice is marked as paid, the transaction doesn't immediately get applied to the invoice. After 5 or so minutes, the payment is then applied and the invoice is marked as paid. Not a big deal but is there any reason for this delay? Why is it not applied immediately?

 

It sounds like the payment could not be applied to the invoices, possibly because you did not specify which invoices amount to apply? So the transaction is added as a credit, and the cron finds credits on the system when it runs and applies them to any open invoices automatically--not necessarily to the invoices you tried to pay.

 

 

public function validate(array $get, array $post) {
    $hash = $this->ifSet($get['hash']);
    $amount = $this->ifSet($get['amount']);

    ...
    // Perform validation logic
    ...

    // Return these fields (your values will vary)
    return array(
        'client_id' => $this->ifSet($get[2]),
        'amount' => $amount,
        'status' => "approved", // ('approved','declined','void','error','pending','refunded', or 'returned')
        'reference_id' => null,
        'transaction_id' => null, 
        'parent_transaction_id' => null,
        'invoices' => array(array('id' => $this->ifSet($get['invoiceid']), 'amount' => $amount)) // An array of arrays, each referencing the ID and Amount of each invoice paid
    );
}

CarrotPay::validate should return the field ' invoices ' containing each invoice ID and amount to apply to it. 

 

 

Also, even after the payment is made, the product isn't automatically provisioned as is expected. Within the client area it just shows up as a pending product and I need to manually click 'activate' within the admin area. I double checked to make sure there wasn't anything requiring manual approval or intervention. Any ideas?

You may want to check the cron is enabled for Provision Paid Pending Services under [settings] -> [Automation]. If the invoice is paid in full, that task will automatically provision within the set interval.

Link to comment
Share on other sites

It sounds like the payment could not be applied to the invoices, possibly because you did not specify which invoices amount to apply? So the transaction is added as a credit, and the cron finds credits on the system when it runs and applies them to any open invoices automatically--not necessarily to the invoices you tried to pay.

 

 

Does this mean payments to invoices are SUPPOSED to be applied instantly and not need to wait for the cron to execute?

 

 

 

You may want to check the cron is enabled for Provision Paid Pending Services under [settings] -> [Automation]. If the invoice is paid in full, that task will automatically provision within the set interval.

 

I checked and it  does appear to be enabled.

 

Any ideas as to why I might be seeing these problems?

Link to comment
Share on other sites

FYI, here is the callback URL and the code within the validate function.

 

public function validate(array $get, array $post) {

		#
		# TODO: Verify the get/post data, then return the transaction
		#
		#

		$invoices = $this->ifSet($get['invoices']);
		$amount = $this->ifSet($get['amount']);
		$currency = $this->ifSet($get['currency']);
		$hash = $this->ifSet($get['hash']);

		$hashSeed = $this->ifSet($this->meta['secret']);
		

		// Log the successful response
		$this->log($this->ifSet($_SERVER['REQUEST_URI']), serialize($post), "output", true);
		
		$word = $invoices; // Used to create Hash
		$isSpecial = ""; //Null Default Value
		//Check if special currency
		if ($currency == "CRT") {
			$isSpecial = "#";
		}
		$price = $amount.":".$isSpecial.$currency; //Converts the pirce into carrots
		$hashval = hash_word($word,$price,$hashSeed);

		#Checks if transaction is valid
		$status="error";
		if ($hashval == $hash) {
			$status = "approved"; // set to '1' if valid
		}	
		
		return array(
			'client_id' => $this->ifSet($get[2]),
			'amount' => $amount,
			'currency' => $currency,
			'status' => $status, // ('approved','declined','void','error','pending','refunded', or 'returned')
			'reference_id' => null,
			'transaction_id' => $hash, 
			'parent_transaction_id' => null,
			'invoices' => array(array('id' => $this->ifSet($get['invoices']), 'amount' => $amount)) // An array of arrays, each referencing the ID and Amount of each invoice paid
		);

	}

Does that look right?

Link to comment
Share on other sites

Payments would be applied immediately to invoices that the gateway has returned, yes.

 

 

Does that look right?

 

No, it looks like you aren't passing the invoices back properly.

 

Based on the URL you included:

invoices=YToxOntpOjA7YToyOntzOjI6ImlkIjtpOjQ7czo2OiJhbW91bnQiO3M6NjoiOS42Nzc0Ijt9fQ==

$get['invoices'] is still serialized and encoded. You need to decode and unserialize the invoices before returning them. It is possible to pay multiple invoices at once, and to apply partial amounts to each, so it is important to set these appropriately. Other gateways implement a helper method, usually called unserializeInvoices(), to set the invoices back into the expected format.

 

The end result would look something like:

return array(
   ...
   'invoices' => array(
       array('id' => 1, 'amount' => 25.00),
       array('id' => 2, 'amount' => 5.25),
       array('id' => 7, 'amount' => 10.06)
   )
   ...
);
Link to comment
Share on other sites

My method is the default example given in the template.

	public function success(array $get, array $post) {
		
		#
		# TODO: Return transaction data, if possible
		#

		$this->Input->setErrors($this->getCommonError("unsupported"));

		return array();

	}

But I have previously tried returning some values in the array but there do not appear to be any values to obtain from $get or $post.

 

Give then following 'validate' method, what should I be putting in the 'success' method?

	public function validate(array $get, array $post) {

		$invoices = $this->ifSet($get['invoices']);
		$amount = $this->ifSet($get['amount']);
		$currency = $this->ifSet($get['currency']);
		$hash = $this->ifSet($get['hash']);

		$hashSeed = $this->ifSet($this->meta['secret']);
		

		// Log the successful response
		$this->log($this->ifSet($_SERVER['REQUEST_URI']), serialize($post), "output", true);
		
		$word = $invoices; // Used to create Hash
		$isSpecial = ""; //Null Default Value
		//Check if special currency
		if ($currency == "CRT") {
			$isSpecial = "#";
		}
		$price = $amount.":".$isSpecial.$currency; //Converts the pirce into carrots
		$hashval = hash_word($word,$price,$hashSeed);

		#Checks if transaction is valid
		$status="error";
		if ($hashval == $hash) {
			$status = "approved"; // set to '1' if valid
		}		
		
		return array(
			'client_id' => $this->ifSet($get[2]),
			'amount' => $amount,
			'currency' => $currency,
			'status' => $status, // ('approved','declined','void','error','pending','refunded', or 'returned')
			'reference_id' => null,
			'transaction_id' => $hash, 
			'parent_transaction_id' => null,
			'invoices' => unserialize(base64_decode($invoices)) // An array of arrays, each referencing the ID and Amount of each invoice paid
		);

	}

And what happens in case of failed (invalid) verification? How can I output a meaningful message to the client upon success/failure of a transaction?

Link to comment
Share on other sites

Your success method should return (almost) the same data that is returned in validate. That should be described in the comments for that method. However, you don't need to validate the data, as it isn't used for processing any transaction.

 

If the payment failed, the customer should be aware of that failure while they're still on CarrotPay's website, and they wouldn't be returned back to Blesta until the payment went through successfully. The success method assumes the payment was successful because they shouldn't be viewing the 'payment received' page if it weren't.

 

In the unlikely case that they return to Blesta and your validation method determines the payment is invalid, it is probably because of one of the following:

  1. your validation method is not correctly determining the payment's validity
  2. someone modified the payment data enroute causing it to no longer be valid (e.g. man in the middle attack) 

 

 

Ah.. there is one thing. Even though the payment goes through, there is still an error displayed.

 

http://i.imgur.com/F9jQElN.png

 

The error message in that screenshot is shown because you had set it in the success method with:

$this->Input->setErrors($this->getCommonError("unsupported"));

By default, that 'payment received' page says something to the effect of "Thank You! Your payment is being processed." In order to see that message, your success method needs to return data.

 

The 'payment received' page isn't designed to display anything besides that success message. While you can still set an error message to be displayed, I don't think it would be necessary. You can set a custom error message with:

$this->Input->setErrors(array('payment' => array('failed' => "The payment could not be processed!"));
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...