Create nested relationships in Laravel - php

I've been trying to discover whether or not it's currently possible to do the following.
Basically I have three models: Customer, Invoice, and Unit. (Customer has one Invoice; an Invoice has many Units).
I was wondering if it was possible to seamlessly do the following:
...
# Let's pretend that further up the code that
# Customer has all the properties set and now
# being saved in the database.
$Customer->save();
# This is currently trying set the values of
# 'units' to a non-existent table column in
# 'invoices' table.
$Customer->invoice()->create([
"units" => [
[
"name" => "Unit 1",
"amount" => 999.99,
"tax" => "20%",
"type" => "+"
]
]
]);

If you setup the relationships in the model as you described them, then you can achieve it close to what you've shown. Something like this:
Customer
public function invoice()
{
return $this->hasOne(Invoice::class);
}
Invoice
public function units()
{
return $this->hasMany(Unit::class);
}
Then calling:
$customer->invoice()->units()->create([
"name" => "Unit 1",
"amount" => 999.99,
"tax" => "20%",
"type" => "+"
]);
Should work.

If you setup the relationships in the model as you described them, then you can achieve it close to what you've shown. Something like this:
Customer
public function invoice()
{
return $this->hasOne(Invoice::class);
}
Invoice
public function units()
{
return $this->hasMany(Unit::class);
}
Then calling:
$customer->invoice()->units()->create([
"name" => "Unit 1",
"amount" => 999.99,
"tax" => "20%",
"type" => "+"
]);
Should work.
if you call save on the invoice first, then you should just call $invoice->units()
$invoice = $customer->invoice()->create([
"campany" => "Unit 1",
"dat" => "2022-02-18"
]);
$invoice->units()->create([
"name" => "Unit 1",
"amount" => 999.99,
"tax" => "20%",
"type" => "+"
]);
... instead of doing it through the Customer. so $invoice = $customer->invoice()->create(...); before, and then add units.

Related

Stripe Bi-Weekly Subscription Description

Using Stripe's PHP API I was able to create a bi-weekly subscription for my customers but I'm having an issue, the "description" on all subscriptions is defaulting to "Subscription creation" and I can't seem to find a way to add a description although I thought the following code worked in the past (I updated the API since then). Please see my code below
case "BiWeekly":
try {
$product = \Stripe\Product::create([
"name" => "NEO Bi-Weekly Payments for $cname",
"type" => "service",
"statement_descriptor" => "NEO",
]);
$plan = \Stripe\Plan::create([
"product" => $product->id,
"amount" => $totalAmount,
"currency" => "usd",
"interval" => "week",
"interval_count" => 2,
"usage_type" => "licensed",
]);
$subscription = \Stripe\Subscription::create([
"customer" => $customer->id,
"items" => [["plan" => $plan->id]],
"metadata" => array("Name" => $cname, "For" => "NEO Bi-Wkly Pymts")
]);
} catch(\Stripe\Error\Card $e) {
$body = $e->getJsonBody();
$err = $body['error'];
header("Location: https://www.neo.com/declined/");
exit();
};
break;
Any help would be greatly appreciated!
As clarified in the comments - description='Subscription creation' is on the corresponding PaymentIntent (not the Subscription).
There's no easy way to do this - it's not possible to specify a description when creating the Subscription to automatically populate on the corresponding PaymentIntent.
What I suggest is to :
Create the Subscription with metadata
listen for the invoice.payment_succeeded webhook event - https://stripe.com/docs/webhooks
The invoice.payment_succeeded will contain the metadata from step 1 in lines and the payment_intent
Example
...
"lines": {
"object": "list",
"data": [
{
"id": "il_...",
"object": "line_item",
...
"metadata": {
"subscription_metadata": "some_value"
},
...
"payment_intent": "pi_...",
...
using the data from step 3, make a request to update the PaymentIntent's description
On a side note, you should no longer be creating Plans (which is deprecated), but should be creating Prices instead.

How do I get PayPal to work with Laravel PHP?

Seems like nobody in the last 5 years has tried to get PayPal to work with a Laravel site on PHP so I am forcibly asking now.
I am using this package: https://github.com/srmklive/laravel-paypal
And I am sending PayPal this:
array:6 [▼
"items" => array:2 [▼
0 => array:3 [▼
"name" => "Product 1"
"price" => 9.99
"qty" => 1
]
1 => array:3 [▼
"name" => "Product 2"
"price" => 4.99
"qty" => 2
]
]
"return_url" => "https://github.com/payment/success"
"invoice_id" => "PAYPALDEMOAPP_1"
"invoice_description" => "Order #PAYPALDEMOAPP_1 Invoice"
"cancel_url" => "https://github.com/cart"
"total" => 19.97
]
These values are purely for testing of course but they should work.
However, I get this error:
array:3 [▼
"type" => "error"
"message" => ""
"paypal_link" => null
]
My code looks like this:
public function start()
{
$provider = new ExpressCheckout();
$data = [];
$data['items'] = [
[
'name' => 'Product 1',
'price' => 9.99,
'qty' => 1,
],
[
'name' => 'Product 2',
'price' => 4.99,
'qty' => 2,
],
];
$data['return_url'] = 'https://github.com/payment/success';
$data['invoice_id'] = 'PAYPALDEMOAPP_' . 1;
$data['invoice_description'] = "Order #{$data['invoice_id']} Invoice";
$data['cancel_url'] = 'https://github.com/cart';
// $data['return_url'] = url('/payment/success');
// $data['cancel_url'] = url('/cart');
$total = 0;
foreach($data['items'] as $item) {
$total += $item['price'] * $item['qty'];
}
$data['total'] = $total;
$response = $provider->setExpressCheckout($data);
dd($response);
return redirect($response['paypal_link']);
}
These values are exactly the same as the one used by https://github.com/srmklive/laravel-paypal-demo/
Which is a working demo!
I looked into it further an found where the requests are sent within the package and it sends a POST Request to https://api-3t.sandbox.paypal.com/nvp and when I recreate the request with the same postdata in postman, I get ACK=Failure&L_ERRORCODE0=81002&L_SHORTMESSAGE0=Unspecified%20Method&L_LONGMESSAGE0=Method%20Specified%20is%20not%20Supported&L_SEVERITYCODE0=Error which is what I believe the real error to be.
If anyone could help, that'd be great!
The package you linked to is ancient, 2 generations out of date as far as PayPal APIs go. Don't use it.
Here is the current PHP SDK (not laravel specific): https://github.com/paypal/Checkout-PHP-SDK
It should be used to create two routes on your server, one to 'Set up Transaction', and one to 'Capture Transaction'. Here is a guide: https://developer.paypal.com/docs/checkout/reference/server-integration/
Those 2 routes should be called by this front-end code: https://developer.paypal.com/demo/checkout/#/pattern/server
Double check that credentials are configured correctly.
validate_ssl may also need to be set to false: https://github.com/srmklive/laravel-paypal/issues/229#issuecomment-472755054

Yii2 Load schedule using unclead/yii2-multiple-input

I am using unclead / yii2-multiple-input widget.
I want to generate different number of rows with values from my database.
How can i do this?
I can design my columns in view and edit data manualy after page generated. But miss how to program the number of rows and its values in the view.
My code in view:
<?= $form->field($User, 'User')->widget(MultipleInput::className(), [
'min' => 0,
'max' => 4,
'columns' => [
[
'name' => 'name',
'title' => 'Name',
'type' => 'textInput',
'options' => [
'onchange' => $onchange,
],
],
[
'name' => 'birth',
'type' => \kartik\date\DatePicker::className(),
'title' => 'Birth',
'value' => function($data) {
return $data['day'];
},
'options' => [
'pluginOptions' => [
'format' => 'dd.mm.yyyy',
'todayHighlight' => true
]
]
],
]
])->label(false);
How can I make (for example) 8 rows with different values, and also have the ability to edit/remove/update some of them?
You need to look into the documentation as it says that you need to assign a separate field into the model which will store all the schedule in form of JSON and then provide it back to the field when editing/updating the model.
You have not added the appropriate model to verify how are you creating the field User in your given case above. so, i will try to create a simple example which will help you implement it in your scenario.
For Example.
You have to store a user in the database along with his favorite books.
User
id, name, email
Books
id, name
Create a field/column in you User table with the name schedule of type text, you can write a migration or add manually.
Add it to the rules in the User model as safe.
like below
public function rules() {
return [
....//other rules
[ [ 'schedule'] , 'safe' ]
];
}
Add the widget to the newly created column in ActiveForm
see below code
echo $form->field($model,'schedule')->widget(MultipleInput::class,[
'max' => 4,
'columns' => [
[
'name' => 'book_id',
'type' => 'dropDownList',
'title' => 'Book',
'items' => ArrayHelper::map( Books::find()->asArray()->all (),'id','name'),
],
]
]);
When saving the User model convert the array to JSON string.
like below
if( Yii::$app->request->isPost && $model->load(Yii::$app->request->post()) ){
$model->schedule = \yii\helpers\Json::encode($model->schedule);
$model->save();
}
Override the afterFind() of the User model to covert the json back to the array before loading the form.
like below
public function afterFind() {
parent::afterFind();
$this->schedule = \yii\helpers\Json::decode($this->schedule);
}
Now when saved the schedule field against the current user will have the JSON for the selected rows for the books, as many selected, for example, if I saved three books having ids(1,2,3) then it will have json like below
{
"0": {
"book_id": "1"
},
"2": {
"book_id": "2"
},
"3": {
"book_id": "3"
}
}
The above JSON will be converted to an array in the afterFind() so that the widget loads the saved schedule when you EDIT the record.
Now go to your update page or edit the newly saved model you will see the books loaded automatically.

Adding items to Stripe invoices with PHP

I followed the API reference here but when I try to create and invoice, I get this error:
missing an item
So I created some items and now I want to add an item to the invoice but I don't understand how to write it in the invoice array.
Right now I have this code. It creates the invoice but with ALL the items I created until now. How do I specify the item within the function?
try {
require_once('./Stripe/init.php');
\Stripe\Stripe::setApiKey("sk_test_iP2aEsMDAc0ZCh5XGdE6AOnt");
$customer = \Stripe\Invoice::create(array(
"customer" => "cus_CML6eYLJif4EJ5",
"billing" => "charge_automatically",
"description" => "Testing invoices"
));
echo 'Invoice created';
}
More info
\Stripe\Stripe::setApiKey("sk_test_iP2aEsMDAc0ZCh5XGdE6AOnt");
\Stripe\InvoiceItem::create(array(
"customer" => "cus_CML6eYLJif4EJ5",
"amount" => 2500,
"currency" => "usd",
"description" => "One-time setup fee")
);

Yii2 Elasticsearch extension - how do I handle type mapping?

I want to be able to store a json object in my ES index. Here's an example of what I'm trying to store (this a serialized model, a request body that is sent to ES):
"{"id":218,"name":"Test2","category_id":1,"address":"Pushkin street","phone":null,"site":null,"location":{"lat":64,"lon":70},"city":"Heaven","description":"Super company","tags":["#test1","#test2"]}"
When I try to store it (via the extension, of course), here's the error that ES returns:
"{"error":{"root_cause":[{"type":"mapper_parsing_exception","reason":"failed to parse [location]"}],"type":"mapper_parsing_exception","reason":"failed to parse [location]","caused_by":{"type":"illegal_argument_exception","reason":"unknown property [lat]"}},"status":400}"
It seems that I am unable to do so without having a specific type mapping, like in the docs:
https://www.elastic.co/guide/en/elasticsearch/reference/1.4/mapping-object-type.html
However, I don't seem to find a way to provide that mapping inside the model. The extension's documentation doesn't really say anything about it.
So, my question is: do I need it at all, and if I do, how?
Appreciate all feedback.
I'll assume your model is \yii\elasticsearch\ActiveRecord. You'll need to describe its attributes:
public function attributes()
{
return [
'name',
'category_id',
'address',
'phone',
'site',
'location',
'city',
'description',
'tags'
];
}
Don't forget to configure index() and type(). In the following example type is my_address.
Then you'll need to create an index with proper field mapping. Here's what your mapping should look like:
"mappings" : {
"my_address" : {
"properties" : {
"name" : { "type" : "string"},
"category_id" : { "type" : "integer"},
"address" : { "type" : "string"},
"phone" : { "type" : "string"},
"site" : { "type" : "string"},
"location" : { "type" : "geo_point"},
"city" : { "type" : "string"},
"description" : { "type" : "string"},
"tags" : { "type" : "string"}
}
}
}
Note three things:
Location is of type geo_point.
Tags are declared as string. This will also allow them to be arrays of strings.
I didn't include the id field. If it's unique, I suggest you just set your yii model's id to the necessary value ($model->primaryKey = '123'). Otherwise your ES model will have its internal id set to something like AVDXmfJ3Ou7LzqD1DDMj and also have an id field which is not very convenient.
I encourage you to take a closer look at the mappings - they are very important when it comes to configuring how exactly the strings are being analyzed.
UPDATE: You don't really describe the mapping anywhere in your model. Do it in a migration - similar to creating tables in SQL.
In case you using ElasticSearch ActiveRecord , you could define a method for setupMapping
Class BookIndex extends yii\elasticsearch\ActiveRecord
{
/**
* sets up the index for this record
*
*/
public static function setUpMapping()
{
$db = static::getDb();
//in case you are not using elasticsearch ActiveRecord so current class extends database ActiveRecord yii/db/activeRecord
// $db = yii\elasticsearch\ActiveRecord::getDb();
$command = $db->createCommand();
/*
* you can delete the current mapping for fresh mapping but this not recommended and can be dangrous.
*/
// $command->deleteMapping(static::index(), static::type());
$command->setMapping(static::index(), static::type(), [
static::type() => [
// "_id" => ["path" => "id", "store" => "yes"],
"properties" => [
'name' => ["type" => "string"],
'author_name' => ["type" => "string"],
'publisher_name' => ["type" => "string"],
'created_at' => ["type" => "long"],
'updated_at' => ["type" => "long"],
'status' => ["type" => "long"],
],
],
]);
}
}
Later on you just need to call this method any time you want to apply the new mapping.

Categories