I am trying to migrate my payment flow from the charges API to payment intents.
The stripe migration docs indicate in step 2 you need to create the payment intent on the server https://stripe.com/docs/payments/payment-intents/quickstart#creating-with-manual-confirmation
If the payment fails and user attempt another payment then I'd be creating multiple payment intents for the same order.
Is there a way that I can reuse the same payment intent if let's say I created the payment intent as soon as the amount is known and then stored the payment intent for that order in a database table using the manual confirmation method?
So something like this:
// create intent
$intent = \Stripe\PaymentIntent::create([
'amount' => 1099,
'currency' => 'usd',
'confirmation_method' => 'manual',
],[
'idempotency_key' => $orderId,
]);
// Update orders table with payment intent id and set order status to unpaid
Then when user makes payment:
# vendor using composer
require_once('vendor/autoload.php');
\Stripe\Stripe::setApiKey(getenv('STRIPE_SECRET_KEY'));
header('Content-Type: application/json');
# retrieve json from POST body
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str);
#$intent = null;
$order = // get from database
$intent = \Stripe\PaymentIntent::retrieve($order->payment_intent_id);
try {
if (isset($json_obj->payment_method_id)) {
# Create the PaymentIntent
#$intent = \Stripe\PaymentIntent::create([
# 'payment_method' => $json_obj->payment_method_id,
# 'amount' => 1099,
# 'currency' => 'usd',
# 'confirmation_method' => 'manual',
# 'confirm' => true,
#]);
# Instead of creating a new payment intent we update the previously saved PaymentIntent
\Stripe\PaymentIntent::update($intent->id,
[
'payment_method' => $json_obj->payment_method_id,
'confirm' => true,
]
);
}
if (isset($json_obj->payment_intent_id)) {
$intent = \Stripe\PaymentIntent::retrieve(
$json_obj->payment_intent_id
);
$intent->confirm();
}
generatePaymentResponse($intent);
} catch (\Stripe\Error\Base $e) {
# Display error on client
echo json_encode([
'error' => $e->getMessage()
]);
}
function generatePaymentResponse($intent) {
# Note that if your API version is before 2019-02-11, 'requires_action'
# appears as 'requires_source_action'.
if ($intent->status == 'requires_action' &&
$intent->next_action->type == 'use_stripe_sdk') {
# Tell the client to handle the action
echo json_encode([
'requires_action' => true,
'payment_intent_client_secret' => $intent->client_secret
]);
} else if ($intent->status == 'succeeded') {
# The payment didn’t need any additional actions and completed!
# Handle post-payment fulfillment
echo json_encode([
"success" => true
]);
} else {
# Invalid status
http_response_code(500);
echo json_encode(['error' => 'Invalid PaymentIntent status']);
}
}
Any tips and advice appreciated.
Related
I'm not using Stripe.JS because I need custom UI.
I have collected card information from my customer. I'm using the PHP SDK.
My workflow is
create customer
add/attach payment method to customer
create payment intent
Every works fine. Now the issue that is if the end user uses a 3D secure card, I get a next_action in the response
"next_action": {
"type": "use_stripe_sdk",
"use_stripe_sdk": {
"type": "three_d_secure_redirect",
"stripe_js": "https://hooks.stripe.com/redirect/authenticate/xxxxxx?client_secret=src_client_secret_xxxxxxx",
"source": "src_xxxxxx"
}
},
"next_source_action": {
"type": "use_stripe_sdk",
"use_stripe_sdk": {
"type": "three_d_secure_redirect",
"stripe_js": "https://hooks.stripe.com/redirect/authenticate/src_xxxxxx?client_secret=src_client_secret_xxxxxx",
"source": "src_xxxxxx"
}
},
The documentation then relies on Stripe.JS for this step to open a verification popup. I'm not using Stripe.JS for the first part of the implementation due to custom UI. Can I use Stripe.JS for this part only? Or how do I do this part WITHOUT using Stripe.JS ? How do I know if this next step is successful so I can confirm the payment? I also see in my dev dashboard that the payment so successful (confusing). But the documentation says I still need to confirm that payment intent?
Stripe has documentation about handling 3D Secure manually without Stripe.js which will guide you through what you need to do.
That said, you can create your own custom payment form with Stripe.js using Stripe Elements, which is strongly recommended to increase security and reduce your PCI compliance burden.
check https://stripe.com/docs/payments/payment-intents/migration-synchronous#elements-step-3. You need to use stripe.js, just to handle 3D secure.
Also check this example:
pay.php:
<?php
require './../vendor/autoload.php';
// This is your test secret API key.
\Stripe\Stripe::setApiKey('sk_test_51M5C27LYhXp3iY7Hp8Oz2PRPoUpYXbkTzMcx4xuiEmqayIIJTbGe1uTE2gtPg3mhJJpmoOBnFzZna9eQYr3xmV6T006etitKBC');
header('Content-Type: application/json');
try {
$stripe = new \Stripe\StripeClient('sk_test_51M5C27LYhXp3iY7Hp8Oz2PRPoUpYXbkTzMcx4xuiEmqayIIJTbGe1uTE2gtPg3mhJJpmoOBnFzZna9eQYr3xmV6T006etitKBC');
$cardData = [
'number' => '4000000000003220',
'exp_month' => 12,
'exp_year' => 2029,
'cvc' => '314',
];
$paymentData = [
'amount' => 1400,
'currency' => 'usd',
];
$token = $stripe->tokens->create([
'card' => $cardData,
]);
$paymentMethod = $stripe->paymentMethods->create([
'type' => 'card',
'card' => $cardData,
]);
// Create a PaymentIntent with amount and currency
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => $paymentData['amount'],
'currency' => $paymentData['currency'],
'payment_method' => $paymentMethod,
'confirmation_method' => 'manual' // required to make 3d secure work
]);
$payment = $stripe->paymentIntents->confirm(
$paymentIntent->id,
[
'return_url' => 'YOUR_URL'
]);
if($payment->status == 'succeeded' || $payment->status == 'pending') echo json_encode(['redirect' => './succeed.php']);
// check if the status === requires_action, it means that 3D secure is required
elseif ($payment->status == 'requires_action') {
// Tell the client to handle the action
echo json_encode([
'requiresAction' => true,
'clientSecret' => $paymentIntent->client_secret
]);
exit();
}
else echo json_encode(['unexpected_error' => true]);
$charge = $stripe->charges->create([
'source' => $token,
'currency' => $paymentData['currency'],
'amount' => $paymentData['amount'],
'description' => 'My First Test Charge (created for API docs at https://www.stripe.com/docs/api)',
]);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
pay.js:
// This is your test publishable API key.
const stripe = Stripe("pk_test_51M5C27LYhXp3iY7HJOk6mEZu6caLGHi8QzyaAgMK1Ek1X7W8yeSSkeMVTTg9tO0S8UPnGX4Qo0tSJQzjJuUavbaH00qAFwCPbw");
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmitBackend);
async function handleSubmitBackend(e) {
e.preventDefault();
let response = await fetch("./php/pay.php", {
method: "POST",
});
let result = await response.json();
console.log(result);
if (result.requiresAction) {
// Use Stripe.js to handle the required card action
const { error: errorAction, paymentIntent } = await stripe.handleCardAction(result.clientSecret);
if (errorAction) {
// Show error from Stripe.js in payment form
console.log(errorAction);
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
const serverResponse = await fetch('/pay.php', {
method: 'POST',
});
}
}
else if(result.redirect) {
window.location.href = result.redirect;
}
}
My current Stripe code is as below: as I pass to stripe.
Had been using following Stripe Library:
https://github.com/stripe/stripe-php
Its working fine as of now. Now I need to change code according to SCA. But I am total lost with code update.
My controller
$this->stripe_->add_charge_array($stripe_user_id, $order_info, $price * 100, $currency, $metadata);
$this->stripe_->pay($currency_id, , $stripeToken, $card_id);
$data = $this->stripe_->get_charge_response($stripe_user_id, , $order_id);
$order->setStripeChargeId($data['charge_id']);//Here I save to db
And in stripe library:
public function pay($currency_id, $stripe_token = null, $card_id = null, $account_id = null){
$charge = $this->charge_card($cart['amount'], $cart['currency'], $stripe_token, $description, $cart['metadata'], $currency_id, $account_id);
}
And my charge_card method connecting to Stripe
public function charge_card(){
try {
\Stripe\Stripe::setApiKey($STRIPE_KEY);
$charge = \Stripe\Charge::create(array(
"amount" => intval($amount),
"currency" => $currency,
"source" => $stripe_token, // obtained with Stripe.js
"description" => $description,
"metadata" => $metadata
));
} catch (\Stripe\Error\Base $e) {
// Display a very generic error to the user, and maybe send
// yourself an email
$result = self::put_stripe_error($e);
} catch (Exception $e) {
// Something else happened, completely unrelated to Stripe
self::put_stripe_error($e);
}
return $charge;
}
Stripe library:
https://js.stripe.com/v3/
From what I did understand from documentation is that I need to change my charge_card method like below.
$intent = \Stripe\PaymentIntent::create([
'payment_method' => '{{PAYMENT_METHOD_ID}}',
'customer' => '{{CUSTOMER_ID}}',
'amount' => 1099,
'currency' => 'gbp',
'confirmation_method' => 'manual',
'confirm' => true,
'setup_future_usage' => 'off_session',
]);
But then I get payment method Id in $intent . How to proceed from here.
Documentation misses this.
I have two functions namely charge and subscription, when a successful charge
happens I insert some metadata there look below:
Same for the subscription function:
Then I have a separate file which I will not post here because it only calls to the Charge list API to get all the successful charges to check all the metadatas in there, however I noticed that once I created a subscription via API it's only available there the metadata1 and metadata2.
When I view on the charge created by the subscription nothing was attached to its metadata. How can I attach the metadatas from the subscription to the charge that corresponds to it?
<?php
require_once('stripe-php-master/init.php');
require 'apiKeys.php';
$data = $_REQUEST['data'];
if(isset($data['stripeToken'])){
create_customer($data);
}else{
printResponse($data,"No Token");
}
//Create Customer
function create_customer($data){
try{
\Stripe\Stripe::setApiKey(SK_TEST_KEY);
$customer = \Stripe\Customer::create(array(
"description" => 'Widget Create Customer',
"source" => $data['stripeToken'],
"email" => $data['stripeEmail']
));
$data['customerID'] = $customer->id;
//create_charge($data);
create_subscription($data);
}catch(Exception $e){
echo $e->getMessage();
exit;
}
}//create_customer
//Create charge
function create_charge($data){
try{
\Stripe\Stripe::setApiKey(SK_TEST_KEY);
$settings = [
'currency' => 'aud',
'amount'=>5500,
'description' => 'Widget Payment for ' .$data['widget_name'],
'customer' => $data['customerID'],
'metadata'=>["metadata1" => $data['metadata1'],"metadata2" => $data['metadata2']]
];
// Get the payment token ID submitted by the form:
$charge = \Stripe\Charge::create($settings);
printResponse($data, $charge->status);
}catch(Exception $e){
printResponse($data,$e->getMessage());
exit;
}
}//create_charge
//Create subscription
function create_subscription($data){
try{
\Stripe\Stripe::setApiKey(SK_TEST_KEY);
$settings = [
'customer' => $data['customerID'],
'metadata'=>["metadata1" => $data['metadata1'],"metadata2" => $data['metadata2']],
'items' => [
[
'plan' => $data['widget_class']
]
]
];
$subscription = \Stripe\Subscription::create($settings);
printResponse($data,$subscription->status);
}catch(Exception $e){
printResponse($data,$e->getMessage());
exit;
}
}
function printResponse($data,$status){
//CREATE Response JSON
print_r(json_encode([
'customer_name'=> $data['customer_name'],
'metadata1' => $data['metadata1'],
'metadata2'=> $data['metadata2'],
'payment_status'=>$status
]));
}
?>
I just want the when someone subscribes the charge from the subscription event has the metadata1 and metadata2 attached to it.
I'm sorry for my english.
I'm try to create a charge with a connected account in stripe using standalone account. I'm connecting my accounts using this article in stripe: Sample OAuth Applications. I have 3 accounts: A) Main account. In this account are the clients of our app. Account B and account C are other accounts connected to account A (I see this two accounts connect in the settings of account A. That tells me that two account is already connected, right?). All user's app is in account A and we want share to account B and C, that's our target.
When the accounts has been connected we save in our DB the acces_token and refresh token. The docs of shared customers says that we need create a token with the customer_id and the account_id in order to create a token with the credit card info of customer but when we execute the code it returns an error:
The provided key 'sk_test_********************uJ3l' does not have access to account 'acct_--------------' (or that account does not exist). Application access may have been revoked.
We have test a several combinations of variables pass to api and the result always returns an error.
What are doing wrong in this case. Can you help us???
This is the code that we using: All the comments has been the another test that we try:
public function createtransaction($customer_id, $paymentDesc, $destinationAccount) {
$errors = array();
try {
$secret_key = $this->get_secret_key();
\Stripe\Stripe::setApiKey($secret_key);
// THIS ONLY FOR TESTING PURPOSE. WE WANT SEE IF CAN ACCESS TO CONNECTED ACCOUNT
// Fetching an account just needs the ID as a parameter
// WHIT ACCESS_TOKEN WORKS THE RETRIEVE METHOD
// $account = \Stripe\Account::retrieve('access_token_from_db');
// WHIT ACCOUNT_ID DOES NOT. RETURNS AN ERROR
// $account = \Stripe\Account::retrieve('acct_***********');
// echo "<pre>------";
// print_r($account);
// echo "-----</pre>";
// HERE ONLY WE CAN TEST IF WE CAN CREATE A CUSTOMER. THIS IS ONLY TO CHECK IF THE ACCOUNT IS CONNECTED. THE CUSTOMER ALREADY EXISTS IN MAIN ACCOUNT
// Recommended: sending API key with every request
// \Stripe\Customer::create(
// array("description" => "example#stripe.com"),
// array("api_key" => 'sk_test_***************') // account's access token from the Connect flow
// SAME. WITH stripe_account DOES NOT WORK. WHIT api_key PARAM WORKS WELL
// );
// THE DOCS SAYS WE NEED CREATE A TOEKN WITH customer AND stripe_account, BUT IT RETURNS AN ERROR OF PERMISSION
// Create a Token from the existing customer on the platform's account
// $token = \Stripe\Token::create(
// array("customer" => $customer_id),
// array("stripe_account" => 'acct_********************') // id of the connected account
// );
// echo "<pre>------";
// print_r($token);
// echo "-----</pre>";
// ONE COMBINATION. DOES NOT WORK
// var_dump($customer_id, $paymentDesc, $destinationAccount);
// $charge = \Stripe\Charge::create(array(
// "amount" => $paymentDesc['total'] * 100,
// "currency" => "usd",
// "source" => $token,
// "description" => $paymentDesc['description'],
// 'destination' => $destinationAccount
// ));
//
//
// IT WORKS IN THE MAIN ACCOUNT BUT IT IS NOT OUR GOAL, BUT WE CAN TRANSFER THE MONEY TO ANOTHER CONNECTED ACCOUNT
// $charge = \Stripe\Charge::create(array(
// 'amount' => $paymentDesc['total'] * 100,
// 'currency' => 'usd',
// 'customer' => $customer_id,
// "description" => $paymentDesc['description']
// ));
// ANOTHER COMBINATION. DOES NOT WORK
/*$charge = \Stripe\Charge::create(array(
'amount' => $paymentDesc['total'] * 100,
'currency' => 'usd',
"description" => $paymentDesc['description'],
'customer' => $customer_id
), array('stripe_account' => 'acct_************'));*/
// ANOTHER COMBINATION. DOES NOT WORK
/*$charge = \Stripe\Charge::create(array(
'amount' => $paymentDesc['total'] * 100,
'currency' => 'usd',
"description" => $paymentDesc['description'],
'customer' => $customer_id
), array('api_key' => 'ACCESS_TOKEN_IN_DB'));
echo "<pre>------";
print_r($charge);
echo "-----</pre>";
return array('charge' => $charge);*/
} catch(Stripe_CardError $e) {
$errors = array('error' => false, 'message' => 'Card was declined.', 'e' => $e);
} catch (Stripe_InvalidRequestError $e) {
$errors = array('error' => false, 'message' => 'Invalid parameters were supplied to Stripe\'s API', 'e' => $e);
} catch (Stripe_AuthenticationError $e) {
$errors = array('error' => false, 'message' => 'Authentication with Stripe\'s API failed!', 'e' => $e);
} catch (Stripe_ApiConnectionError $e) {
$errors = array('error' => false, 'message' => 'Network communication with Stripe failed', 'e' => $e);
} catch (Stripe_Error $e) {
$errors = array('error' => false, 'message' => 'Stripe error. Something wrong just happened!', 'e' => $e);
} catch (Exception $e) {
$errors = array('error' => false, 'message' => 'An error has ocurred getting info customer.', 'e' => $e);
}
return $errors;
}
You don't need to save the access_token and refresh_token, only the stripe_user_id containing the connected account id is required to work with Connect.
You can use your platform's secret API key for all the requests and just pass the connected account id to the Stripe-account header as shown in the shared customers documentation. More generally this header can be added to almost every API request to have it directed at a connected account:
https://stripe.com/docs/connect/authentication#authentication-via-the-stripe-account-header
In your case you'd do:
// Create a Token from the existing customer on the platform's account
$sharedToken = \Stripe\Token::create(
array("customer" => CUSTOMER_ID, "card" => CARD_ID),
array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID) // id of the connected account
);
$sharedCustomer = \Stripe\Customer::create(array(
"description" => "Customer for noah.robinson#example.com",
"source" => $sharedToken),
array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID)
);
You can then create a charge from this shared customer in the same way:
\Stripe\Charge::create(array(
"amount" => 2000,
"currency" => "usd",
"customer" => $sharedCustomer),
array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID)
);
This question is maybe similar to THIS and THIS but I'm not entirely sure.
I've made a shopping cart that sends the product details and quantity/total amount to Paypal on checking out. I'm using Laravel 4 and the Omnipay Paypal plugin (Paypal_Express). I can send product details fine using the 'setItems' function and am now looking to pre-populate the credit card field on the Paypal summary page with my User's details.
I have seen in other SO threads such as THIS that other people use the creditCard function to pass details to the Paypal summary credit card info page.
My question: 1) Do you need to be using Paypal_Pro for the creditCard function to worK?
I get this error when I try (call_user_func_array() expects parameter 1 to be a valid callback, class 'Omnipay\Common\GatewayFactory' does not have a method 'creditCard').
I don't want to enter all the credit card details - Just speed the process up by entering the User's Name, Adress etc...
Also I tried changing to Paypal_Pro and it didn't work. (same error as above) I changed config plus gateways in my payment controller.
2)How do you change PayPal_Express to PayPay_Pro?
My code:
public function postPayment() {
$cart = Session::get('cart');
$allProducts = [];
foreach($cart->aContents as $productID=>$quantity){
$product = Product::find($productID);
// get the product id
// load the product from the id
// store data in the allProduct array
$allProducts[] = array('name' => $product->name, 'quantity' => $quantity, 'price'=> $product->price);
}
$cardInput = array(
'first_name' => Input::get('first_name'),
'last_name' => Input::get('last_name'),
'address1' => Input::get('address1'),
'city' => Input::get('city'),
'zip' => Input::get('zip'),
'email' => Input::get('email')
);
$card = Omnipay::creditCard($cardInput);
$params = array(
'cancelUrl' => \URL::to('cancel_order'),
'returnUrl' => \URL::to('payment_success'),
'amount' => Input::get('price'),
'currency' => Input::get('currency'),
'card' => $card,
);
Session::put('params', $params);
Session::save();
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername('tjmusicmanagement-facilitator_api1.gmail.com');
$gateway->setPassword('K2LWQVP2L8472BPY');
$gateway->setSignature('AwTOuAJWzCkdc5PldYeiz.l3iy5UAwOucYW6EFLLA9zUQqXaWyEGbebq');
$gateway->setTestMode(true);
$gateway->setLogoImageUrl(URL::to('images/logoSmall.png'));
$response = $gateway->purchase($params)->setItems($allProducts)->send();
if ($response->isSuccessful()) {
// payment was successful: update database
print_r($response);
} elseif ($response->isRedirect()) {
// redirect to offsite payment gateway
$response->redirect();
} else {
// payment failed: display message to customer
echo $response->getMessage();
}
}
And also the ignited\laravel-omnipay\config.php is unchanged (though I did try changing the driver)
return array(
// The default gateway to use
'default' => 'paypal',
// Add in each gateway here
'gateways' => array(
'paypal' => array(
'driver' => 'PayPal_Express',
'options' => array(
'solutionType' => '',
'landingPage' => '',
'headerImageUrl' => ''
)
)
)
);
Thanks for your tinme!!
EDIT: Here is my getSuccessPayment function where I can hopefully get the users paypal details (just name and address etc) from paypal. But how and where do I specify this?
public function getSuccessPayment()
{
$gateway = Omnipay::create('PayPal_Express');
$gateway->setUsername('lillyloverofwar-facilitator_api1.gmail.com');
$gateway->setPassword('U6LM3SG2MNCA3QE2');
$gateway->setSignature('AJVP9tUtdotIeVt82RpcG7n9ld-tAdCG1Ramb1u8yZECHhSpiXc0BO04');
$gateway->setTestMode(true);
$params = Session::get('params');
$response = $gateway->completePurchase($params)->send();
$paypalResponse = $response->getData(); // this is the raw response object
if(isset($paypalResponse['PAYMENTINFO_0_ACK']) && $paypalResponse['PAYMENTINFO_0_ACK'] === 'Success') {
// return View::make('successfulPayment')->with($params);
// Session::flush();
// Response
// print_r($paypalResponse);
} else {
//Failed transaction
}
// FLUSHING SESSION HERE GETS AN ERROR
// Session::flush();
return View::make('successfulPayment');
}
1) I get this error when I try (call_user_func_array() expects parameter 1 to be a valid callback, class 'Omnipay\Common\GatewayFactory' does not have a method 'creditCard').
You can't use credit cards on the PayPal Express gateway, only on Pro or REST. I recommend that you use the REST gateway not the Pro gateway (REST supersedes Pro and has more features).
I don't want to enter all the credit card details - Just speed the process up by entering the User's Name, Adress etc...
There is no need to do that if you are using PayPal Express anyway, because PayPal will provide you the necessary details after the user has gone through the PayPal login process and authorized the transaction.
Also I tried changing to Paypal_Pro and it didn't work. (same error as above) I changed config plus gateways in my payment controller.
2)How do you change PayPal_Express to PayPay_Pro?
I suggest you have a look at my fork of the omnipay-paypal gateway, https://github.com/delatbabel/omnipay-paypal -- on the accept-paypal-payments branch there are additional commits (sent as a PR to the main repository but not merged yet) with additional features such as using the REST gateway for either Credit Card or PayPal purchases, and additional API documentation including code examples on how to use the REST gateway.
Here is the code example for using the Rest gateway for a purchase transaction with a credit card:
// Create a gateway for the PayPal RestGateway
// (routes to GatewayFactory::create)
$gateway = Omnipay::create('RestGateway');
// Initialise the gateway
$gateway->initialize(array(
'clientId' => 'MyPayPalClientId',
'secret' => 'MyPayPalSecret',
'testMode' => true, // Or false when you are ready for live transactions
));
// Create a credit card object
// DO NOT USE THESE CARD VALUES -- substitute your own
// see the documentation in the class header.
$card = new CreditCard(array(
'firstName' => 'Example',
'lastName' => 'User',
'number' => '4111111111111111',
'expiryMonth' => '01',
'expiryYear' => '2020',
'cvv' => '123',
'billingAddress1' => '1 Scrubby Creek Road',
'billingCountry' => 'AU',
'billingCity' => 'Scrubby Creek',
'billingPostcode' => '4999',
'billingState' => 'QLD',
));
// Do a purchase transaction on the gateway
$transaction = $gateway->purchase(array(
'amount' => '10.00',
'currency' => 'AUD',
'description' => 'This is a test purchase transaction.',
'card' => $card,
));
$response = $transaction->send();
if ($response->isSuccessful()) {
echo "Purchase transaction was successful!\n";
$sale_id = $response->getTransactionReference();
echo "Transaction reference = " . $sale_id . "\n";
}