Applepay.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. namespace Longyi\Pay\Applepay\Payment;
  3. use GuzzleHttp\Client;
  4. use Illuminate\Support\Facades\Log;
  5. use Webkul\Payment\Payment\Payment;
  6. use Webkul\Sales\Models\Order;
  7. use Webkul\Sales\Repositories\OrderRepository;
  8. use Webkul\Sales\Repositories\InvoiceRepository;
  9. class Applepay extends Payment
  10. {
  11. /**
  12. * Payment method code
  13. *
  14. * @var string
  15. */
  16. protected $code = 'applepay';
  17. protected $clientId;
  18. protected $apikey;
  19. protected $webhookId;
  20. protected $currentOrder;
  21. protected $prefix = 'QQS';
  22. public $createOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders';
  23. public $captureOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}/capture';
  24. public $detailOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}';
  25. public $updateOrderApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}';
  26. public $sigApi = 'https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature';
  27. public $orderTrackApi = 'https://api-m.sandbox.paypal.com/v2/checkout/orders/{id}/track';
  28. public $addTrackApi = 'https://api-m.sandbox.paypal.com/v1/shipping/trackers-batch';
  29. public function __construct(
  30. protected InvoiceRepository $invoiceRepository,
  31. protected OrderRepository $orderRepository,
  32. )
  33. {
  34. if ($this->getConfigData('mode') == 'live') {
  35. $this->createOrderApi = 'https://api-m.paypal.com/v2/checkout/orders';
  36. $this->captureOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}/capture';
  37. $this->detailOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}';
  38. $this->updateOrderApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}';
  39. $this->sigApi = 'https://api-m.paypal.com/v1/notifications/verify-webhook-signature';
  40. $this->orderTrackApi = 'https://api-m.paypal.com/v2/checkout/orders/{id}/track';
  41. $this->addTrackApi = 'https://api-m.paypal.com/v1/shipping/trackers-batch';
  42. }
  43. $this->clientId = $this->getConfigData('client_id');
  44. $this->apikey = $this->getConfigData('secret_id');
  45. $this->webhookId = $this->getConfigData('webhook_id');
  46. }
  47. /**
  48. * Get redirect url.
  49. */
  50. public function getRedirectUrl()
  51. {
  52. }
  53. public function setOrder($order = null): self
  54. {
  55. $this->currentOrder = $order;
  56. return $this;
  57. }
  58. public function getOrder()
  59. {
  60. return $this->currentOrder;
  61. }
  62. public function createGatewayOrder()
  63. {
  64. return $this->createOrder($this->getOrder(), ['merchantOrderId' => $this->getOrder()->increment_id]);
  65. }
  66. public function createOrder($order, $override)
  67. {
  68. $billingAddressLines = $this->getAddressLines($order->billing_address->address);
  69. $data = [
  70. 'intent' => 'CAPTURE',
  71. 'purchase_units' => [
  72. [
  73. 'reference_id' => $override['merchantOrderId'],
  74. 'invoice_id' => $override['merchantOrderId'],
  75. 'amount' => [
  76. 'currency_code' => $order->order_currency_code,
  77. 'value' => $this->formatCurrencyValue((float) $order->sub_total + $order->tax_total + ($order->selected_shipping_rate ? $order->selected_shipping_rate->price : 0) - $order->discount_amount),
  78. 'breakdown' => [
  79. 'item_total' => [
  80. 'currency_code' => $order->order_currency_code,
  81. 'value' => $this->formatCurrencyValue((float) $order->sub_total),
  82. ],
  83. 'shipping' => [
  84. 'currency_code' => $order->order_currency_code,
  85. 'value' => $this->formatCurrencyValue((float) ($order->selected_shipping_rate ? $order->selected_shipping_rate->price : 0)),
  86. ],
  87. 'tax_total' => [
  88. 'currency_code' => $order->order_currency_code,
  89. 'value' => $this->formatCurrencyValue((float) $order->tax_total),
  90. ],
  91. 'discount' => [
  92. 'currency_code' => $order->order_currency_code,
  93. 'value' => $this->formatCurrencyValue((float) $order->discount_amount),
  94. ],
  95. ],
  96. ],
  97. 'items' => $this->getLineItems($order),
  98. 'shipping' => [
  99. 'address' => [
  100. 'address_line_1' => current($billingAddressLines),
  101. 'address_line_2' => last($billingAddressLines),
  102. 'admin_area_2' => $order->shipping_address->city,
  103. 'admin_area_1' => $order->shipping_address->state,
  104. 'postal_code' => $order->shipping_address->postcode,
  105. 'country_code' => $order->shipping_address->country,
  106. ],
  107. ],
  108. ]
  109. ],
  110. 'payment_source' => [
  111. 'apple_pay' => [
  112. 'name' => $order->customer_full_name,
  113. 'email_address' => $order->customer_email
  114. ]
  115. ]
  116. ];
  117. $client = new Client();
  118. $response = $client->request('POST', $this->createOrderApi, [
  119. 'headers' => [
  120. 'Accept' => 'application/json',
  121. 'Content-Type' => 'application/json',
  122. 'Authorization' => 'Basic '.base64_encode("{$this->clientId}:{$this->apikey}")
  123. ],
  124. 'json' => $data,
  125. 'timeout' => 30,
  126. 'verify' => false
  127. ]);
  128. $responseBody = $response->getBody()->getContents();
  129. Log::channel('payment')->info('appleypay createPayment response', [
  130. 'order_id' => $order->increment_id ?? null,
  131. 'status' => $response->getStatusCode(),
  132. 'body' => json_decode($responseBody, true),
  133. 'data' => $data
  134. ]);
  135. $resultObject = json_decode($responseBody);
  136. return $resultObject->id;
  137. }
  138. public function captureAndVerify($order, $transactionId)
  139. {
  140. if ($order->status != Order::STATUS_PENDING) {
  141. return new \Exception('order status is error');
  142. }
  143. $additional = $order->payment->additional;
  144. $token = $additional['gateway_order_id'];
  145. if ($transactionId != $token) {
  146. return new \Exception('applepay order token is error');
  147. }
  148. return $this->capturePayment($token);
  149. }
  150. public function capturePayment($id)
  151. {
  152. $data = [];
  153. $client = new Client();
  154. $response = $client->request('POST', str_replace('{id}', $id, $this->captureOrderApi), [
  155. 'headers' => [
  156. 'Accept' => 'application/json',
  157. 'Content-Type' => 'application/json',
  158. 'Authorization' => 'Basic '.base64_encode("{$this->clientId}:{$this->apikey}")
  159. ],
  160. 'timeout' => 30,
  161. 'verify' => false
  162. ]);
  163. $responseBody = $response->getBody()->getContents();
  164. Log::channel('payment')->info('appleypay capturePayment response', [
  165. 'status' => $response->getStatusCode(),
  166. 'body' => json_decode($responseBody, true),
  167. 'data' => $data
  168. ]);
  169. $resultObject = json_decode($responseBody);
  170. if ($resultObject->purchase_units[0]->payments->captures[0]->status == 'COMPLETED') {
  171. return ['transaction_id' => $resultObject->id];
  172. } else {
  173. throw new \Exception("applepay capture is error");
  174. }
  175. }
  176. /**
  177. * Return cart items.
  178. *
  179. * @param string $cart
  180. * @return array
  181. */
  182. protected function getLineItems($order)
  183. {
  184. $lineItems = [];
  185. foreach ($order->items as $item) {
  186. $lineItems[] = [
  187. 'unit_amount' => [
  188. 'currency_code' => $order->order_currency_code,
  189. 'value' => $this->formatCurrencyValue((float) $item->price),
  190. ],
  191. 'quantity' => $item->qty_ordered,
  192. 'name' => $item->name,
  193. 'sku' => $item->sku,
  194. 'category' => $item->getTypeInstance()->isStockable() ? 'PHYSICAL_GOODS' : 'DIGITAL_GOODS',
  195. ];
  196. }
  197. return $lineItems;
  198. }
  199. public function webhookSignature($header, $data)
  200. {
  201. $data = [
  202. 'transmission_id' => $header['Paypal-Transmission-Id'],
  203. 'transmission_time' => $header['Paypal-Transmission-Time'],
  204. 'cert_url' => $header['Paypal-Cert-Url'],
  205. 'auth_algo' => $header['Paypal-Auth-Algo'],
  206. 'transmission_sig' => $header['Paypal-Transmission-Sig'],
  207. 'webhook_id' => $this->webhookId,
  208. 'webhook_event' => $data
  209. ];
  210. $client = new Client();
  211. $response = $client->request('POST', $this->sigApi, [
  212. 'headers' => [
  213. 'Accept' => 'application/json',
  214. 'Content-Type' => 'application/json',
  215. 'Authorization' => 'Basic '. base64_encode("{$this->apikey}:{$this->secret}")
  216. ],
  217. 'json' => $data,
  218. 'timeout' => 30,
  219. 'verify' => false
  220. ]);
  221. $resultObject = json_decode($response->getBody()->getContents());
  222. if ($resultObject->verification_status == 'SUCCESS') {
  223. return true;
  224. } else {
  225. return false;
  226. }
  227. }
  228. //成功修改状态
  229. public function paymentSucceeded($orderId, $transactionId)
  230. {
  231. $order = $this->orderRepository->findOneByField('increment_id', $orderId);
  232. if (!$order->id) {
  233. return false;
  234. }
  235. if ($order->status != Order::STATUS_PENDING) {
  236. return false;
  237. }
  238. $order->status = Order::STATUS_PROCESSING;
  239. $order->save();
  240. if ($order->payment) {
  241. $additional = $order->payment->additional;
  242. $additional['transaction_id'] = $transactionId;
  243. $order->payment->additional = $additional;
  244. $order->payment->save();
  245. }
  246. if ($order->canInvoice()) {
  247. $this->invoiceRepository->create($this->prepareInvoiceData($order));
  248. }
  249. }
  250. /**
  251. * Prepares order's invoice data for creation.
  252. */
  253. protected function prepareInvoiceData($order): array
  254. {
  255. $invoiceData = [
  256. 'order_id' => $order->id,
  257. 'invoice' => ['items' => []],
  258. ];
  259. foreach ($order->items as $item) {
  260. $invoiceData['invoice']['items'][$item->id] = $item->qty_to_invoice;
  261. }
  262. return $invoiceData;
  263. }
  264. protected function getAddressLines($address)
  265. {
  266. $address = explode(PHP_EOL, $address, 2);
  267. $addressLines = [current($address)];
  268. if (isset($address[1])) {
  269. $addressLines[] = str_replace(["\r\n", "\r", "\n"], ' ', last($address));
  270. } else {
  271. $addressLines[] = '';
  272. }
  273. return $addressLines;
  274. }
  275. public function formatCurrencyValue($number): float
  276. {
  277. return round((float) $number, 2);
  278. }
  279. public function isAvailable()
  280. {
  281. if (! parent::isAvailable()) {
  282. return false;
  283. }
  284. $userAgent = request()->header('User-Agent');
  285. if (! $userAgent) {
  286. return false;
  287. }
  288. $appleDevices = [
  289. 'iPhone',
  290. 'iPad',
  291. 'iPod',
  292. 'Macintosh',
  293. 'Mac OS X',
  294. ];
  295. foreach ($appleDevices as $device) {
  296. if (strpos($userAgent, $device) !== false) {
  297. return true;
  298. }
  299. }
  300. return true;
  301. }
  302. }