Yii2 贝宝支付集成
Yii2 paypal payment integration
我正在使用此 https://www.yiiframework.com/extension/bitcko/yii2-bitcko-paypal-api#usage 和 yii2 来启用付款我的代码如下所示。
public function actionMakePayment(){
if(!Yii::$app->user->getIsGuest()){
// Setup order information array
$params = [
'order'=>[
'description'=>'Payment description',
'subtotal'=>45,
'shippingCost'=>0,
'total'=>45,
'currency'=>'USD',
]
];
// In case of payment success this will return the payment object that contains all information about the order
// In case of failure it will return Null
Yii::$app->PayPalRestApi->processPayment($params);
}else{
Yii::$app->response->redirect(Url::to(['site/signup'], true));
}
一切都按照我的预期进行,此调用将类似这样的内容返回给 dom。
{ "id": "PAYID-LTKUAVA8WK14445NN137182H", "intent": "sale", "state": "approved", "cart": "9RE74926AX5730813", "payer": { "payment_method": "paypal", "status": "UNVERIFIED", "payer_info": { "first_name": "Susi", "last_name": "Flo", "payer_id": "KWPDGYRP2KCK4", "shipping_address": { "recipient_name": "Susi Flo", "line1": "Suso", "line2": "bldg", "city": "Spring hill", "state": "FL", "postal_code": "34604", "country_code": "US" }, "phone": "3526003902", "country_code": "US" } }, "transactions": [ { "amount": { "total": "45.00", "currency": "USD", "details": { "subtotal": "45.00", "shipping": "0.00", "insurance": "0.00", "handling_fee": "0.00", "shipping_discount": "0.00" } }, "payee": { "merchant_id": "NHN6S6KT4FF6N", "email": "arunwebber2-facilitator@gmail.com" }, "description": "Payment description", "invoice_number": "5cd5404d624a9", "soft_descriptor": "PAYPAL *TESTFACILIT", "item_list": { "items": [ { "name": "Item one", "price": "45.00", "currency": "USD", "tax": "0.00", "quantity": 1 } ], "shipping_address": { "recipient_name": "Susi Flo", "line1": "Suso", "line2": "bldg", "city": "Spring hill", "state": "FL", "postal_code": "34604", "country_code": "US" } }, "related_resources": [ { "sale": { "id": "6LN25215GP1183020", "state": "completed", "amount": { "total": "45.00", "currency": "USD", "details": { "subtotal": "45.00", "shipping": "0.00", "insurance": "0.00", "handling_fee": "0.00", "shipping_discount": "0.00" } }, "payment_mode": "INSTANT_TRANSFER", "protection_eligibility": "ELIGIBLE", "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE", "transaction_fee": { "value": "2.43", "currency": "USD" }, "receipt_id": "3896118010137330", "parent_payment": "PAYID-LTKUAVA8WK14445NN137182H", "create_time": "2019-05-10T09:30:10Z", "update_time": "2019-05-10T09:30:10Z", "links": [ { "href": "https://api.sandbox.paypal.com/v1/payments/sale/6LN25215GP1183020", "rel": "self", "method": "GET" }, { "href": "https://api.sandbox.paypal.com/v1/payments/sale/6LN25215GP1183020/refund", "rel": "refund", "method": "POST" }, { "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LTKUAVA8WK14445NN137182H", "rel": "parent_payment", "method": "GET" } ], "soft_descriptor": "PAYPAL *TESTFACILIT" } } ] } ], "create_time": "2019-05-10T09:11:48Z", "update_time": "2019-05-10T09:30:10Z", "links": [ { "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LTKUAVA8WK14445NN137182H", "rel": "self", "method": "GET" } ] }
如何将其存储到我的数据库中?对于特定用户 ID,我可以通过此获取用户 ID。
echo Yii::$app->user->id;
我想将此值与用户 ID 一起存储,我该怎么做?并向用户发送支付成功消息:)
更新
看起来组件 class 需要完全复制和编辑才能正确覆盖 checkOut()
方法,因为在该方法中访问的 属性 $apiContext
是private
而不是 $protected
所以你要么复制整个组件并将它放在你的 frontend/components
目录中并相应地更改它然后使用。
最重要的是 class 的设计和编写也很糟糕,如果您使用我在我的 Yii2 项目中一直使用的以下组件会更好。我没有删除额外的代码,并按照答案中的原样粘贴了文件。您可以 remove/comment 与 BalanceHistory
TransactionHistory
相关的部分和电子邮件部分。您需要通过 composer 安装 paypal checkout sdk 或在您的 composer.json
中添加以下内容
"paypal/paypal-checkout-sdk": "1.0.1"
贝宝组件
<?php
namespace frontend\components;
use Yii;
use common\models\{
User,
BalanceHistory,
TransactionHistory
};
use yii\base\Component;
use common\components\Helper;
use PayPalCheckoutSdk\Core\{
PayPalHttpClient,
SandboxEnvironment,
ProductionEnvironment
};
use PayPalCheckoutSdk\Orders\{
OrdersGetRequest,
OrdersCreateRequest,
OrdersCaptureRequest
};
class Paypal extends Component
{
/**
* The Pyapal Client Id
*
* @var mixed
*/
public $clientId;
/**
* The Paypal client Secret
*
* @var mixed
*/
public $clientSecret;
/**
* API context object
*
* @var mixed
*/
private $httpClient; // paypal's http client
/**
* @var mixed
*/
private $user_id;
/**
* Override Yii's object init()
*
* @return null
*/
public function init()
{
$this->httpClient = new PayPalHttpClient(
Yii::$app->params['paypal']['mode'] == 'sandbox' ?
new SandboxEnvironment($this->clientId, $this->clientSecret) :
new ProductionEnvironment($this->clientId, $this->clientSecret)
);
$this->user_id = Yii::$app->user->id;
Yii::info("User: {$this->user_id} Init PayPal", 'paypal');
}
/**
* Returns the context object
*
* @return object
*/
public function getClient()
{
return $this->httpClient;
}
/**
* Set the payment methods and other objects necessary for making the payment
*
* @param decimal $price the amount to be charged
*
* @return string $approvalUrl
*/
public function createOrder($price)
{
//create order request
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
setlocale(LC_MONETARY, 'en_US.UTF-8');
$price = sprintf('%01.2f', $price);
Yii::info("User: {$this->user_id} Setting payment for amount: {$price}", 'paypal');
//build the request body
$requestBody = [
'intent' => 'CAPTURE',
'purchase_units' =>
[
0 =>
[
'amount' =>
[
'currency_code' => 'USD',
'value' => $price,
],
],
],
'application_context' => [
'shipping_preference' => 'NO_SHIPPING'
]
];
$request->body = $requestBody;
//call PayPal to set up a transaction
$client = $this->getClient();
$response = $client->execute($request);
return json_encode($response->result, JSON_PRETTY_PRINT);
}
/**
* @param $orderId
*/
public function getOrder($orderId)
{
// 3. Call PayPal to get the transaction details
$request = new OrdersGetRequest($orderId);
$client = $this->getClient();
$response = $client->execute($request);
return json_encode($response->result, JSON_PRETTY_PRINT);
}
/**
* Retrieves Order Capture Details for the given order ID
*
* @param string $orderId the payment id of the transaction
*
* @return mixed
*/
public function captureOrder($orderId)
{
$request = new OrdersCaptureRequest($orderId);
//Call PayPal to capture an authorization
$client = $this->getClient();
$transaction = Yii::$app->db->beginTransaction();
try {
$response = $client->execute($request);
//get payment variables for email
$paymentId = $response->result->id;
$paymentStatus = $response->result->status;
$paypalTransaction = $response->result->purchase_units[0]->payments->captures[0];
$payedAmount = $paypalTransaction->amount->value;
$txnId = $paypalTransaction->id;
$userId = $this->user_id;
//get the user
$model = User::findOne($userId);
$profile = $model->businessProfile;
$prevBalance = $profile->balance;
if ($paymentStatus == 'COMPLETED') {
Yii::info("User: {$userId} payment amount:{$payedAmount} approved updating balance.", 'paypal');
//update balance
$newBalance = $profile->updateBalance($payedAmount);
Yii::info("User: {$userId} balance updated.", 'paypal');
$data = [
'amount' => $payedAmount,
'type' => TransactionHistory::BALANCE_ADDED,
'description' => "Funds added to account",
'user' => [
'id' => $userId,
'balance' => $newBalance,
],
];
Yii::info("User: {$userId} adding transaction history.", 'paypal');
TransactionHistory::add($data);
//update subscription status if required
if ($profile->subscription_status !== 'active') {
$profile->updateStatus('active');
}
Yii::info("User: {$userId} adding balance history:{$payedAmount}.", 'paypal');
//send the success email to the user and admin
$this->sendNotification($model, $response->result);
//set session flash with success
Yii::$app->session->setFlash(
'success',
'Your Payment is processed and you will receive an email with the details shortly'
);
} else {
Yii::warning("User: {$userId} payment amount:{$payedAmount} NOT approved.", 'paypal');
//send the error email to the user and admin
$this->sendNotification($model, $response->result, 'error');
//set session flash with error
Yii::$app->session->setFlash(
'danger',
'Your Payment was not approved, an email has been sent with the details.'
);
}
//update balance history
BalanceHistory::add(
$profile->user_id,
$prevBalance,
$payedAmount,
$paymentId,
$paymentStatus,
$txnId,
$response
);
//commit the transaction
$transaction->commit();
Yii::info(
"User: {$userId} payment Success prevBalance: {$prevBalance} payedAmount:{$payedAmount}.",
'paypal'
);
return json_encode($response->result, JSON_PRETTY_PRINT);
} catch (\Exception $e) {
//roll back the transaction
$transaction->rollBack();
Yii::error("ERROR EXCEPTION", 'paypal');
Yii::error($e->getMessage(), 'paypal');
Yii::error($e->getTraceAsString(), 'paypal');
//send error email to the developers
Helper::sendExceptionEmail(
"TC : Exception on PayPal Balance",
$e->getMessage(),
$e->getTraceAsString()
);
//set session flash with error
Yii::$app->session->setFlash('danger', $e->getMessage());
}
}
/**
* Sends Success Email for the transaction
*
* @param \common\models\User $model the user model object
* @param $response the paypal Order Capture object
* @param string $type the type of the notification to be sent
*
* @return null
*/
public function sendNotification(
\common\models\User $model,
$response,
$type = 'success'
) {
Yii::info("User: {$this->user_id} Sending notifications type:{$type}", 'paypal');
$paymentId = $response->id;
$paymentStatus = $response->status;
$paypalTransaction = $response->purchase_units[0]->payments->captures[0];
$payedAmount = $paypalTransaction->amount->value;
//payment creation time
$paymentCreateTime = new \DateTime(
$paypalTransaction->create_time,
new \DateTimeZone('UTC')
);
//payment update time
$paymentUpdateTime = new \DateTime(
$paypalTransaction->update_time,
new \DateTimeZone('UTC')
);
//payer/billing info for email
$payerInfo = $response->payer;
$payerEmail = $payerInfo->email_address;
$payerFirstName = $payerInfo->name->given_name;
$payerLastName = $payerInfo->name->surname;
$billingInfo = [
'billing_info' => [
'email' => $payerEmail,
'full_name' => "$payerFirstName $payerLastName",
],
];
if (property_exists($response->purchase_units[0], 'shipping')) {
$payerAddress = property_exists($response->purchase_units[0]->shipping->address, 'address_line_1');
$isStateAvailable = property_exists($response->purchase_units[0]->shipping->address, 'admin_area_1');
$isPostCodeAvailable = property_exists($response->purchase_units[0]->shipping->address, 'postal_code');
$iscountryCodeAvailable = property_exists($response->purchase_units[0]->shipping->address, 'country_code');
//@codingStandardsIgnoreStart
$payerState = $isStateAvailable ? $response->purchase_units[0]->shipping->address->admin_area_1 : 'NA';
$payerPostalCode = $isPostCodeAvailable ? $response->purchase_units[0]->shipping->address->postal_code : 'NA';
$payerCountryCode = $iscountryCodeAvailable ? $response->purchase_units[0]->shipping->address->country_code : 'NA';
//@codingStandardsIgnoreEnd
$billingInfo['billing_info'] = array_merge(
$billingInfo['billing_info'],
[
'address' => $payerAddress,
'state' => $payerState,
'country' => $payerCountryCode,
'post_code' => $payerPostalCode,
]
);
}
//email params
$data = [
'user' => [
'email' => $model->email,
'name' => $model->username,
],
'payment_id' => $paymentId,
'amount' => $payedAmount,
'status' => $paymentStatus,
'create_time_utc' => $paymentCreateTime,
'update_time_utc' => $paymentUpdateTime,
];
$data = array_merge($data, $billingInfo);
//check the notification email type and set params accordingly
if ($type == 'success') {
$txnId = $paypalTransaction->id;
$data['txn_id'] = $txnId;
$subject = Yii::$app->id . ': Your Account has been recharged.';
$view = '@frontend/views/user/mail/payment-complete';
} else {
$subject = Yii::$app->id . ': Transaction failed.';
$view = '@frontend/views/user/mail/payment-failed';
}
Yii::info("User: {$this->user_id} Sending email to user:{$model->email} type: {$type}", 'paypal');
//send email to user
$model->sendEmail($subject, $view, $data, $model->email);
//send notification to admin for Payment Received
$data['user']['email'] = Yii::$app->params['adminEmail'];
$subject = ($type == 'success') ?
Yii::$app->id . ': New Transaction in Account.' :
Yii::$app->user->id . ': A Transaction Failed for the user.';
Yii::info(
"User: {$this->user_id} Sending email to admin " . Yii::$app->params['adminEmail'] . " type: {$type}",
'paypal'
);
//send admin email
$model->sendEmail($subject, $view, $data, Yii::$app->params['adminEmail']);
}
}
用法
您可以分别调用 createOrder
和 captureOrder
。我用 ajax 方法使用它,所以我有如下定义的单独操作
/**
* Displays fail message to the user
*
* @param string $token the cancel token
*
* @return mixed
* @throws \Exception
*/
public function actionPaymentCancel($token)
{
Yii::warning("Payment Cancel : token: {$token}.", 'paypal');
return $this->render(
'payment-cancelled',
[
'data' => $token,
]
);
}
/**
* Shows the payment details & success message to the user
*
* @param string $paymentId the payment id
*
* @return mixed
* @throws \Exception
*/
public function actionPaymentComplete($paymentId)
{
$history = BalanceHistory::findOne(['payment_id' => $paymentId]);
return $this->render(
'payment-complete',
[
'data' => $history,
]
);
}
/**
* Captures the Paypal order and verifies it
*
* @param string $orderId the Paypal order object's id
*
* @return mixed
*/
public function actionCaptureOrder($orderId)
{
$orderInfo = Yii::$app->paypal->captureOrder($orderId);
return $orderInfo;
}
/**
* Creates the order and
*
* @param string $amount the price of the order
*
* @return mixed
*/
public function actionCreateOrder($amount)
{
if (!Yii::$app->user->isGuest) {
$order = Yii::$app->paypal->createOrder($amount);
return $order;
}
throw new Exception("You are not logged in.", 404);
}
/**
* Executes the payement and checkouts to the paypal to confirm
*
* @param string $token the paypal token
*
* @return mixed
*/
public function actionPaymentExecute($orderId)
{
//get transaction details
$details = Yii::$app->paypal->getOrder($orderId);
$details = json_decode($details);
//added check for duplicate hits to return url from Paypal
if (null !== BalanceHistory::transactionExists($orderId)) {
//redirect to payment complete
return $this->redirect(['payment-complete', 'paymentId' => $orderId]);
}
if ($details->status == 'COMPLETED') {
//redirect to payment complete
return $this->redirect(['payment-complete', 'paymentId' => $orderId]);
} else {
//redirect to the payment failed page
return $this->redirect(['payment-failed', 'paymentId' => $orderId]);
}
}
另外请记住,您需要为 paypal 网关声明一个带有实时和本地 ENV 的参数,这会变成沙箱环境 ON/OFF。
params-local.php
<?php
'paypal'=>[
'sandbox'=>true
]
?>
params.php
<?php
'paypal'=>[
'sandbox'=>false
]
?>
Paypal PHP-SDK 为您提供 setCustom()
添加自定义字段值,您可以使用它发送用户 ID,然后在付款后通过交易对象中的响应检索它已执行。
您使用的只是一个使用 Paypal SDK 函数的自定义组件,您应该扩展 class bitcko\paypalrestapi\PayPalRestApi.php
以覆盖函数 checkOut()
并添加 ->setCustom(Yii::$app->user->id)
到此 line 中的链,因为它不提供任何设置自定义字段的方法,因此只需将方法的整个代码复制到新的 class 并添加上面的内容。
您的 class 应该如下所示。
注意:在 common/components
文件夹中添加文件。
<?php
namespace common\components;
use bitcko\paypalrestapi\PayPalRestApi as PayPalBase;
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Exception\PayPalConnectionException;
use yii\helpers\Url;
use Yii;
class PaypalRestApi extends PayPalBase
{
public function checkOut($params)
{
$payer = new Payer();
$payer->setPaymentMethod($params['method']);
$orderList = [];
foreach ($params['order']['items'] as $orderItem) {
$item = new Item();
$item->setName($orderItem['name'])
->setCurrency($orderItem['currency'])
->setQuantity($orderItem['quantity'])
->setPrice($orderItem['price']);
$orderList[] = $item;
}
$itemList = new ItemList();
$itemList->setItems($orderList);
$details = new Details();
$details->setShipping($params['order']['shippingCost'])
->setSubtotal($params['order']['subtotal']);
$amount = new Amount();
$amount->setCurrency($params['order']['currency'])
->setTotal($params['order']['total'])
->setDetails($details);
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription($params['order']['description'])
->setCustom(Yii::$app->user->id)
->setInvoiceNumber(uniqid());
$redirectUrl = Url::to([$this->redirectUrl], true);
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl("$redirectUrl?success=true")
->setCancelUrl("$redirectUrl?success=false");
$payment = new Payment();
$payment->setIntent($params['intent'])
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions(array($transaction));
try {
$payment->create($this->apiContext);
return \Yii::$app->controller->redirect($payment->getApprovalLink());
} catch (PayPalConnectionException $ex) {
// This will print the detailed information on the exception.
//REALLY HELPFUL FOR DEBUGGING
\Yii::$app->response->format = \yii\web\Response::FORMAT_HTML;
\Yii::$app->response->data = $ex->getData();
}
}
}
现在,将 PayPalRestApi
组件 class 中的 common/config/main.php
或 frontend/config/main.php
组件的配置更改为您创建的新 class
'components'=> [
...
'PayPalRestApi'=>[
'class'=>'common\components\PayPalRestApi',
]
...
]
所以现在您可以使用
获得相同的用户 ID
$response = \yii\helpers\Json::decode( Yii::$app->PayPalRestApi->processPayment($params));
$user_id = $response['transactions'][0]['custom'];
我正在使用此 https://www.yiiframework.com/extension/bitcko/yii2-bitcko-paypal-api#usage 和 yii2 来启用付款我的代码如下所示。
public function actionMakePayment(){
if(!Yii::$app->user->getIsGuest()){
// Setup order information array
$params = [
'order'=>[
'description'=>'Payment description',
'subtotal'=>45,
'shippingCost'=>0,
'total'=>45,
'currency'=>'USD',
]
];
// In case of payment success this will return the payment object that contains all information about the order
// In case of failure it will return Null
Yii::$app->PayPalRestApi->processPayment($params);
}else{
Yii::$app->response->redirect(Url::to(['site/signup'], true));
}
一切都按照我的预期进行,此调用将类似这样的内容返回给 dom。
{ "id": "PAYID-LTKUAVA8WK14445NN137182H", "intent": "sale", "state": "approved", "cart": "9RE74926AX5730813", "payer": { "payment_method": "paypal", "status": "UNVERIFIED", "payer_info": { "first_name": "Susi", "last_name": "Flo", "payer_id": "KWPDGYRP2KCK4", "shipping_address": { "recipient_name": "Susi Flo", "line1": "Suso", "line2": "bldg", "city": "Spring hill", "state": "FL", "postal_code": "34604", "country_code": "US" }, "phone": "3526003902", "country_code": "US" } }, "transactions": [ { "amount": { "total": "45.00", "currency": "USD", "details": { "subtotal": "45.00", "shipping": "0.00", "insurance": "0.00", "handling_fee": "0.00", "shipping_discount": "0.00" } }, "payee": { "merchant_id": "NHN6S6KT4FF6N", "email": "arunwebber2-facilitator@gmail.com" }, "description": "Payment description", "invoice_number": "5cd5404d624a9", "soft_descriptor": "PAYPAL *TESTFACILIT", "item_list": { "items": [ { "name": "Item one", "price": "45.00", "currency": "USD", "tax": "0.00", "quantity": 1 } ], "shipping_address": { "recipient_name": "Susi Flo", "line1": "Suso", "line2": "bldg", "city": "Spring hill", "state": "FL", "postal_code": "34604", "country_code": "US" } }, "related_resources": [ { "sale": { "id": "6LN25215GP1183020", "state": "completed", "amount": { "total": "45.00", "currency": "USD", "details": { "subtotal": "45.00", "shipping": "0.00", "insurance": "0.00", "handling_fee": "0.00", "shipping_discount": "0.00" } }, "payment_mode": "INSTANT_TRANSFER", "protection_eligibility": "ELIGIBLE", "protection_eligibility_type": "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE", "transaction_fee": { "value": "2.43", "currency": "USD" }, "receipt_id": "3896118010137330", "parent_payment": "PAYID-LTKUAVA8WK14445NN137182H", "create_time": "2019-05-10T09:30:10Z", "update_time": "2019-05-10T09:30:10Z", "links": [ { "href": "https://api.sandbox.paypal.com/v1/payments/sale/6LN25215GP1183020", "rel": "self", "method": "GET" }, { "href": "https://api.sandbox.paypal.com/v1/payments/sale/6LN25215GP1183020/refund", "rel": "refund", "method": "POST" }, { "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LTKUAVA8WK14445NN137182H", "rel": "parent_payment", "method": "GET" } ], "soft_descriptor": "PAYPAL *TESTFACILIT" } } ] } ], "create_time": "2019-05-10T09:11:48Z", "update_time": "2019-05-10T09:30:10Z", "links": [ { "href": "https://api.sandbox.paypal.com/v1/payments/payment/PAYID-LTKUAVA8WK14445NN137182H", "rel": "self", "method": "GET" } ] }
如何将其存储到我的数据库中?对于特定用户 ID,我可以通过此获取用户 ID。
echo Yii::$app->user->id;
我想将此值与用户 ID 一起存储,我该怎么做?并向用户发送支付成功消息:)
更新
看起来组件 class 需要完全复制和编辑才能正确覆盖 checkOut()
方法,因为在该方法中访问的 属性 $apiContext
是private
而不是 $protected
所以你要么复制整个组件并将它放在你的 frontend/components
目录中并相应地更改它然后使用。
最重要的是 class 的设计和编写也很糟糕,如果您使用我在我的 Yii2 项目中一直使用的以下组件会更好。我没有删除额外的代码,并按照答案中的原样粘贴了文件。您可以 remove/comment 与 BalanceHistory
TransactionHistory
相关的部分和电子邮件部分。您需要通过 composer 安装 paypal checkout sdk 或在您的 composer.json
"paypal/paypal-checkout-sdk": "1.0.1"
贝宝组件
<?php
namespace frontend\components;
use Yii;
use common\models\{
User,
BalanceHistory,
TransactionHistory
};
use yii\base\Component;
use common\components\Helper;
use PayPalCheckoutSdk\Core\{
PayPalHttpClient,
SandboxEnvironment,
ProductionEnvironment
};
use PayPalCheckoutSdk\Orders\{
OrdersGetRequest,
OrdersCreateRequest,
OrdersCaptureRequest
};
class Paypal extends Component
{
/**
* The Pyapal Client Id
*
* @var mixed
*/
public $clientId;
/**
* The Paypal client Secret
*
* @var mixed
*/
public $clientSecret;
/**
* API context object
*
* @var mixed
*/
private $httpClient; // paypal's http client
/**
* @var mixed
*/
private $user_id;
/**
* Override Yii's object init()
*
* @return null
*/
public function init()
{
$this->httpClient = new PayPalHttpClient(
Yii::$app->params['paypal']['mode'] == 'sandbox' ?
new SandboxEnvironment($this->clientId, $this->clientSecret) :
new ProductionEnvironment($this->clientId, $this->clientSecret)
);
$this->user_id = Yii::$app->user->id;
Yii::info("User: {$this->user_id} Init PayPal", 'paypal');
}
/**
* Returns the context object
*
* @return object
*/
public function getClient()
{
return $this->httpClient;
}
/**
* Set the payment methods and other objects necessary for making the payment
*
* @param decimal $price the amount to be charged
*
* @return string $approvalUrl
*/
public function createOrder($price)
{
//create order request
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
setlocale(LC_MONETARY, 'en_US.UTF-8');
$price = sprintf('%01.2f', $price);
Yii::info("User: {$this->user_id} Setting payment for amount: {$price}", 'paypal');
//build the request body
$requestBody = [
'intent' => 'CAPTURE',
'purchase_units' =>
[
0 =>
[
'amount' =>
[
'currency_code' => 'USD',
'value' => $price,
],
],
],
'application_context' => [
'shipping_preference' => 'NO_SHIPPING'
]
];
$request->body = $requestBody;
//call PayPal to set up a transaction
$client = $this->getClient();
$response = $client->execute($request);
return json_encode($response->result, JSON_PRETTY_PRINT);
}
/**
* @param $orderId
*/
public function getOrder($orderId)
{
// 3. Call PayPal to get the transaction details
$request = new OrdersGetRequest($orderId);
$client = $this->getClient();
$response = $client->execute($request);
return json_encode($response->result, JSON_PRETTY_PRINT);
}
/**
* Retrieves Order Capture Details for the given order ID
*
* @param string $orderId the payment id of the transaction
*
* @return mixed
*/
public function captureOrder($orderId)
{
$request = new OrdersCaptureRequest($orderId);
//Call PayPal to capture an authorization
$client = $this->getClient();
$transaction = Yii::$app->db->beginTransaction();
try {
$response = $client->execute($request);
//get payment variables for email
$paymentId = $response->result->id;
$paymentStatus = $response->result->status;
$paypalTransaction = $response->result->purchase_units[0]->payments->captures[0];
$payedAmount = $paypalTransaction->amount->value;
$txnId = $paypalTransaction->id;
$userId = $this->user_id;
//get the user
$model = User::findOne($userId);
$profile = $model->businessProfile;
$prevBalance = $profile->balance;
if ($paymentStatus == 'COMPLETED') {
Yii::info("User: {$userId} payment amount:{$payedAmount} approved updating balance.", 'paypal');
//update balance
$newBalance = $profile->updateBalance($payedAmount);
Yii::info("User: {$userId} balance updated.", 'paypal');
$data = [
'amount' => $payedAmount,
'type' => TransactionHistory::BALANCE_ADDED,
'description' => "Funds added to account",
'user' => [
'id' => $userId,
'balance' => $newBalance,
],
];
Yii::info("User: {$userId} adding transaction history.", 'paypal');
TransactionHistory::add($data);
//update subscription status if required
if ($profile->subscription_status !== 'active') {
$profile->updateStatus('active');
}
Yii::info("User: {$userId} adding balance history:{$payedAmount}.", 'paypal');
//send the success email to the user and admin
$this->sendNotification($model, $response->result);
//set session flash with success
Yii::$app->session->setFlash(
'success',
'Your Payment is processed and you will receive an email with the details shortly'
);
} else {
Yii::warning("User: {$userId} payment amount:{$payedAmount} NOT approved.", 'paypal');
//send the error email to the user and admin
$this->sendNotification($model, $response->result, 'error');
//set session flash with error
Yii::$app->session->setFlash(
'danger',
'Your Payment was not approved, an email has been sent with the details.'
);
}
//update balance history
BalanceHistory::add(
$profile->user_id,
$prevBalance,
$payedAmount,
$paymentId,
$paymentStatus,
$txnId,
$response
);
//commit the transaction
$transaction->commit();
Yii::info(
"User: {$userId} payment Success prevBalance: {$prevBalance} payedAmount:{$payedAmount}.",
'paypal'
);
return json_encode($response->result, JSON_PRETTY_PRINT);
} catch (\Exception $e) {
//roll back the transaction
$transaction->rollBack();
Yii::error("ERROR EXCEPTION", 'paypal');
Yii::error($e->getMessage(), 'paypal');
Yii::error($e->getTraceAsString(), 'paypal');
//send error email to the developers
Helper::sendExceptionEmail(
"TC : Exception on PayPal Balance",
$e->getMessage(),
$e->getTraceAsString()
);
//set session flash with error
Yii::$app->session->setFlash('danger', $e->getMessage());
}
}
/**
* Sends Success Email for the transaction
*
* @param \common\models\User $model the user model object
* @param $response the paypal Order Capture object
* @param string $type the type of the notification to be sent
*
* @return null
*/
public function sendNotification(
\common\models\User $model,
$response,
$type = 'success'
) {
Yii::info("User: {$this->user_id} Sending notifications type:{$type}", 'paypal');
$paymentId = $response->id;
$paymentStatus = $response->status;
$paypalTransaction = $response->purchase_units[0]->payments->captures[0];
$payedAmount = $paypalTransaction->amount->value;
//payment creation time
$paymentCreateTime = new \DateTime(
$paypalTransaction->create_time,
new \DateTimeZone('UTC')
);
//payment update time
$paymentUpdateTime = new \DateTime(
$paypalTransaction->update_time,
new \DateTimeZone('UTC')
);
//payer/billing info for email
$payerInfo = $response->payer;
$payerEmail = $payerInfo->email_address;
$payerFirstName = $payerInfo->name->given_name;
$payerLastName = $payerInfo->name->surname;
$billingInfo = [
'billing_info' => [
'email' => $payerEmail,
'full_name' => "$payerFirstName $payerLastName",
],
];
if (property_exists($response->purchase_units[0], 'shipping')) {
$payerAddress = property_exists($response->purchase_units[0]->shipping->address, 'address_line_1');
$isStateAvailable = property_exists($response->purchase_units[0]->shipping->address, 'admin_area_1');
$isPostCodeAvailable = property_exists($response->purchase_units[0]->shipping->address, 'postal_code');
$iscountryCodeAvailable = property_exists($response->purchase_units[0]->shipping->address, 'country_code');
//@codingStandardsIgnoreStart
$payerState = $isStateAvailable ? $response->purchase_units[0]->shipping->address->admin_area_1 : 'NA';
$payerPostalCode = $isPostCodeAvailable ? $response->purchase_units[0]->shipping->address->postal_code : 'NA';
$payerCountryCode = $iscountryCodeAvailable ? $response->purchase_units[0]->shipping->address->country_code : 'NA';
//@codingStandardsIgnoreEnd
$billingInfo['billing_info'] = array_merge(
$billingInfo['billing_info'],
[
'address' => $payerAddress,
'state' => $payerState,
'country' => $payerCountryCode,
'post_code' => $payerPostalCode,
]
);
}
//email params
$data = [
'user' => [
'email' => $model->email,
'name' => $model->username,
],
'payment_id' => $paymentId,
'amount' => $payedAmount,
'status' => $paymentStatus,
'create_time_utc' => $paymentCreateTime,
'update_time_utc' => $paymentUpdateTime,
];
$data = array_merge($data, $billingInfo);
//check the notification email type and set params accordingly
if ($type == 'success') {
$txnId = $paypalTransaction->id;
$data['txn_id'] = $txnId;
$subject = Yii::$app->id . ': Your Account has been recharged.';
$view = '@frontend/views/user/mail/payment-complete';
} else {
$subject = Yii::$app->id . ': Transaction failed.';
$view = '@frontend/views/user/mail/payment-failed';
}
Yii::info("User: {$this->user_id} Sending email to user:{$model->email} type: {$type}", 'paypal');
//send email to user
$model->sendEmail($subject, $view, $data, $model->email);
//send notification to admin for Payment Received
$data['user']['email'] = Yii::$app->params['adminEmail'];
$subject = ($type == 'success') ?
Yii::$app->id . ': New Transaction in Account.' :
Yii::$app->user->id . ': A Transaction Failed for the user.';
Yii::info(
"User: {$this->user_id} Sending email to admin " . Yii::$app->params['adminEmail'] . " type: {$type}",
'paypal'
);
//send admin email
$model->sendEmail($subject, $view, $data, Yii::$app->params['adminEmail']);
}
}
用法
您可以分别调用 createOrder
和 captureOrder
。我用 ajax 方法使用它,所以我有如下定义的单独操作
/**
* Displays fail message to the user
*
* @param string $token the cancel token
*
* @return mixed
* @throws \Exception
*/
public function actionPaymentCancel($token)
{
Yii::warning("Payment Cancel : token: {$token}.", 'paypal');
return $this->render(
'payment-cancelled',
[
'data' => $token,
]
);
}
/**
* Shows the payment details & success message to the user
*
* @param string $paymentId the payment id
*
* @return mixed
* @throws \Exception
*/
public function actionPaymentComplete($paymentId)
{
$history = BalanceHistory::findOne(['payment_id' => $paymentId]);
return $this->render(
'payment-complete',
[
'data' => $history,
]
);
}
/**
* Captures the Paypal order and verifies it
*
* @param string $orderId the Paypal order object's id
*
* @return mixed
*/
public function actionCaptureOrder($orderId)
{
$orderInfo = Yii::$app->paypal->captureOrder($orderId);
return $orderInfo;
}
/**
* Creates the order and
*
* @param string $amount the price of the order
*
* @return mixed
*/
public function actionCreateOrder($amount)
{
if (!Yii::$app->user->isGuest) {
$order = Yii::$app->paypal->createOrder($amount);
return $order;
}
throw new Exception("You are not logged in.", 404);
}
/**
* Executes the payement and checkouts to the paypal to confirm
*
* @param string $token the paypal token
*
* @return mixed
*/
public function actionPaymentExecute($orderId)
{
//get transaction details
$details = Yii::$app->paypal->getOrder($orderId);
$details = json_decode($details);
//added check for duplicate hits to return url from Paypal
if (null !== BalanceHistory::transactionExists($orderId)) {
//redirect to payment complete
return $this->redirect(['payment-complete', 'paymentId' => $orderId]);
}
if ($details->status == 'COMPLETED') {
//redirect to payment complete
return $this->redirect(['payment-complete', 'paymentId' => $orderId]);
} else {
//redirect to the payment failed page
return $this->redirect(['payment-failed', 'paymentId' => $orderId]);
}
}
另外请记住,您需要为 paypal 网关声明一个带有实时和本地 ENV 的参数,这会变成沙箱环境 ON/OFF。
params-local.php
<?php
'paypal'=>[
'sandbox'=>true
]
?>
params.php
<?php
'paypal'=>[
'sandbox'=>false
]
?>
Paypal PHP-SDK 为您提供 setCustom()
添加自定义字段值,您可以使用它发送用户 ID,然后在付款后通过交易对象中的响应检索它已执行。
您使用的只是一个使用 Paypal SDK 函数的自定义组件,您应该扩展 class bitcko\paypalrestapi\PayPalRestApi.php
以覆盖函数 checkOut()
并添加 ->setCustom(Yii::$app->user->id)
到此 line 中的链,因为它不提供任何设置自定义字段的方法,因此只需将方法的整个代码复制到新的 class 并添加上面的内容。
您的 class 应该如下所示。
注意:在 common/components
文件夹中添加文件。
<?php
namespace common\components;
use bitcko\paypalrestapi\PayPalRestApi as PayPalBase;
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Exception\PayPalConnectionException;
use yii\helpers\Url;
use Yii;
class PaypalRestApi extends PayPalBase
{
public function checkOut($params)
{
$payer = new Payer();
$payer->setPaymentMethod($params['method']);
$orderList = [];
foreach ($params['order']['items'] as $orderItem) {
$item = new Item();
$item->setName($orderItem['name'])
->setCurrency($orderItem['currency'])
->setQuantity($orderItem['quantity'])
->setPrice($orderItem['price']);
$orderList[] = $item;
}
$itemList = new ItemList();
$itemList->setItems($orderList);
$details = new Details();
$details->setShipping($params['order']['shippingCost'])
->setSubtotal($params['order']['subtotal']);
$amount = new Amount();
$amount->setCurrency($params['order']['currency'])
->setTotal($params['order']['total'])
->setDetails($details);
$transaction = new Transaction();
$transaction->setAmount($amount)
->setItemList($itemList)
->setDescription($params['order']['description'])
->setCustom(Yii::$app->user->id)
->setInvoiceNumber(uniqid());
$redirectUrl = Url::to([$this->redirectUrl], true);
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl("$redirectUrl?success=true")
->setCancelUrl("$redirectUrl?success=false");
$payment = new Payment();
$payment->setIntent($params['intent'])
->setPayer($payer)
->setRedirectUrls($redirectUrls)
->setTransactions(array($transaction));
try {
$payment->create($this->apiContext);
return \Yii::$app->controller->redirect($payment->getApprovalLink());
} catch (PayPalConnectionException $ex) {
// This will print the detailed information on the exception.
//REALLY HELPFUL FOR DEBUGGING
\Yii::$app->response->format = \yii\web\Response::FORMAT_HTML;
\Yii::$app->response->data = $ex->getData();
}
}
}
现在,将 PayPalRestApi
组件 class 中的 common/config/main.php
或 frontend/config/main.php
组件的配置更改为您创建的新 class
'components'=> [
...
'PayPalRestApi'=>[
'class'=>'common\components\PayPalRestApi',
]
...
]
所以现在您可以使用
获得相同的用户 ID$response = \yii\helpers\Json::decode( Yii::$app->PayPalRestApi->processPayment($params));
$user_id = $response['transactions'][0]['custom'];