If you're using Cashier to provide subscriptions on, say, a sign-up page, then there isn't a(n easy) way to check if the coupon is valid without first creating the user.
public function store()
{
$user = App\User::create($data);
$user->subscription('plan')->withCoupon($invalidCoupon)->create($token);
// etc.
}
This will throw the Stripe\Error\InvalidRequest exception, however your new user is already created and isn't informed as to whether their coupon code was valid or not, which doesn't seem ideal.
For the time being I'm doing:
public function store()
{
$coupon = Request::get('coupon');
if ($coupon !== '')
{
Stripe\Stripe::setApiKey(Config::get('services.stripe.secret'));
Stripe\Coupon::retrieve($coupon);
}
$user = App\User::create($data);
// etc.
}
To throw the exception and catch it in app/Exceptions/Handler.php before the user is created. As you can see this means you have to include a bunch of Stripe specific classes for what I'd guess people have in their Controller.
It seems like the only way around this would be to create a helper function such as isCouponValid($coupon).
If there's a better way I'm all ears but this seems like being able to feed this back to the user is quite important.
Seems to me you could use a database transaction for that instead.
@shabushabu This was one of the possible solutions we looked at, but dismissed because it why should the database care if a coupon code is invalid?
IMHO the validation should take place at the same time (or just after) the form request validation as that's exactly what it does - perform validation which stops the request and feeds back to the user.
The database doesn't care at all, it's you who cares that the coupon is wrong. A transaction is just a tool you can use to achieve what you want.
@shabushabu It is but you're touching the database when you don't need to. As I said before, this doesn't concern a user at all so why should one have to be created just to check if a coupon is valid?
Well, that's just it. As the call to create a user would be within the DB transaction, the user will not be created if the coupon is invalid cause an exception is being thrown. To me that's a valid use-case for using a DB transaction, but you're obviously free to do whatever you want.
I do see what you mean and although I agree it's definitely an option, it's not something I want to pursue.
Like I mentioned in the original issue I've already managed to circumvent creating a user to check if a coupon is valid with the code sample I provided (again, this doesn't need to know about the database or a user to happen) but I'm worried about using the concrete Stripe classes wherever I need to check this so, to me at least, I'd like to think this might be addressed in a future version of Cashier.
For the time being, I've dropped this functionality as it's making my controller difficult to test and separated the sign-up and billing process - that way when it comes to billing there is already a logged in user ready and waiting.
So it's as pretty but you can use the native Stripe functions to validate, which is what I ended up doing.
use Stripe_Coupon;
use Stripe_InvalidRequestError;
...
public function validateCoupon($coupon)
{
try {
Stripe_Coupon::retrieve($coupon);
return true;
} catch(Exception $e) {
return false;
}
}
You can catch the different errors if you want to know why specifically its bad. But I don't care. Its either valid or not.
You will need to fire the API key before you run this
use Stripe;
...
Stripe::setApiKey(Config::get('services.stripe.secret'));
You can see an example here. https://github.com/laravel/cashier/pull/137
Stripe actually has a _valid_ property you can check. I wish I could get this functionality merged. Maybe @taylorotwell will have a second look.
valid (boolean)
Taking account of the above properties, whether this coupon can still be applied to a customer
:+1: Currently this is what I do:
private function isCouponValid($coupon)
{
\Stripe\Stripe::setApiKey(\Config::get('services.stripe.secret'));
try {
$coupon = \Stripe\Coupon::retrieve($coupon);
return $coupon->valid;
} catch(\Exception $e) {
return false;
}
}
@marktopper I'm using the same approach, however, do it like this won't validate against the following error where the coupon currency is different from customer's existing subscriptions or coupons when creating subscriptions.
error:
{
type: "invalid_request_error"
message: "Can't combine currencies on a single customer. This customer has had a subscription, coupon, or invoice item with currency usd"
}
I wonder if there is a way to fully validate the coupon before creating the subscription.
@yingliangzhang: I do not use multiple currencies myself, but in that case I would add this function.
private function isCouponValidInCurrency($coupon, $currency = 'usd')
{
\Stripe\Stripe::setApiKey(\Config::get('services.stripe.secret'));
try {
$coupon = \Stripe\Coupon::retrieve($coupon);
if (!$coupon->valid) {
return false
}
return $coupon->currency == $currency;
} catch(\Exception $e) {
return false;
}
}
Please let me know if this solves your issue.
Closing this issue since this solution https://github.com/laravel/cashier/issues/167#issuecomment-161668919 seems to do the trick.
Most helpful comment
:+1: Currently this is what I do: