Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Wallet Orders #137

Merged
merged 2 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/FinanceOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php declare(strict_types=1);

/**
* Copyright (c), Includable.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/

namespace PKPass;

use ZipArchive;

class FinanceOrder extends PKPass
{
const FILE_TYPE = 'order';
const FILE_EXT = 'order';
const MIME_TYPE = 'application/vnd.apple.finance.order';
const PAYLOAD_FILE = 'order.json';
const HASH_ALGO = 'sha256';

/**
* Sub-function of create()
* This function creates the hashes for the files and adds them into a json string.
*
* @throws PKPassException
*/
protected function createManifest()
{
// Creates SHA hashes for all files in package
$sha = [];
$sha[self::PAYLOAD_FILE] = hash(self::HASH_ALGO, $this->json);

// Creates SHA hashes for string files in each project.
foreach ($this->locales as $language => $strings) {
$sha[$language . '.lproj/' . self::FILE_TYPE . '.strings'] = hash(self::HASH_ALGO, $strings);
}

foreach ($this->files as $name => $path) {
$sha[$name] = hash(self::HASH_ALGO, file_get_contents($path));
}

foreach ($this->remote_file_urls as $name => $url) {
$sha[$name] = hash(self::HASH_ALGO, file_get_contents($url));
}

foreach ($this->files_content as $name => $content) {
$sha[$name] = hash(self::HASH_ALGO, $content);
}

return json_encode((object)$sha);
}

/**
* Creates .pkpass zip archive.
*
* @param string $manifest
* @param string $signature
* @return string
* @throws PKPassException
*/
protected function createZip($manifest, $signature)
{
// Package file in Zip (as .order)
$zip = new ZipArchive();
$filename = tempnam($this->tempPath, self::FILE_TYPE);
if (!$zip->open($filename, ZipArchive::OVERWRITE)) {
throw new PKPassException('Could not open ' . basename($filename) . ' with ZipArchive extension.');
}
$zip->addFromString('signature', $signature);
$zip->addFromString('manifest.json', $manifest);
$zip->addFromString(self::PAYLOAD_FILE, $this->json);

// Add translation dictionary
foreach ($this->locales as $language => $strings) {
if (!$zip->addEmptyDir($language . '.lproj')) {
throw new PKPassException('Could not create ' . $language . '.lproj folder in zip archive.');
}
$zip->addFromString($language . '.lproj/' . self::FILE_TYPE . '.strings', $strings);
}

foreach ($this->files as $name => $path) {
$zip->addFile($path, $name);
}

foreach ($this->remote_file_urls as $name => $url) {
$download_file = file_get_contents($url);
$zip->addFromString($name, $download_file);
}

foreach ($this->files_content as $name => $content) {
$zip->addFromString($name, $content);
}

$zip->close();

if (!file_exists($filename) || filesize($filename) < 1) {
@unlink($filename);
throw new PKPassException('Error while creating order.order. Check your ZIP extension.');
}

$content = file_get_contents($filename);
unlink($filename);

return $content;
}
}
11 changes: 7 additions & 4 deletions src/PKPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
*/
class PKPass
{
const FILE_TYPE = 'pass';
const FILE_EXT = 'pkpass';
const MIME_TYPE = 'application/vnd.apple.pkpass';
/**
* Holds the path to the certificate.
* @var string
Expand Down Expand Up @@ -55,7 +58,7 @@ class PKPass

/**
* Holds the JSON payload.
* @var object|array
* @var string
*/
protected $json;

Expand Down Expand Up @@ -312,7 +315,7 @@ public function create($output = false)

// Output pass
header('Content-Description: File Transfer');
header('Content-Type: application/vnd.apple.pkpass');
header('Content-Type: ' . self::MIME_TYPE);
header('Content-Disposition: attachment; filename="' . $this->getName() . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
Expand All @@ -332,9 +335,9 @@ public function create($output = false)
*/
public function getName()
{
$name = $this->name ?: 'pass';
$name = $this->name ?: self::FILE_TYPE;
if (!strstr($name, '.')) {
$name .= '.pkpass';
$name .= '.' . self::FILE_EXT;
}

return $name;
Expand Down
100 changes: 100 additions & 0 deletions tests/FinanceOrderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PKPass\FinanceOrder;

final class FinanceOrderTest extends TestCase
{
private function validateOrder($orer, $expected_files = [])
{
// basic string validation
$this->assertIsString($orer);
$this->assertGreaterThan(100, strlen($orer));
$this->assertStringContainsString('logo.png', $orer);
$this->assertStringContainsString('ws03-xs-red.jpg', $orer);
$this->assertStringContainsString('manifest.json', $orer);

// try to read the ZIP file
$temp_name = tempnam(sys_get_temp_dir(), 'pkpass');
file_put_contents($temp_name, $orer);
$zip = new ZipArchive();
$res = $zip->open($temp_name);
$this->assertTrue($res, 'Invalid ZIP file.');
$this->assertEquals(count($expected_files), $zip->numFiles);

// extract zip to temp dir
$temp_dir = $temp_name . '_dir';
mkdir($temp_dir);
$zip->extractTo($temp_dir);
$zip->close();
echo $temp_dir;
foreach ($expected_files as $file) {
$this->assertFileExists($temp_dir . DIRECTORY_SEPARATOR . $file);
}
}

public function testBasicGeneration()
{
$pass = new FinanceOrder(__DIR__ . '/fixtures/example-certificate.p12', 'password');
$pass->setData([
"createdAt" => "2024-02-01T19:45:50+00:00",
"merchant" => [
"displayName" => "Luma",
"merchantIdentifier" => "merchant.com.pkpass.unit-test",
"url" => "https://demo-store.test/",
'logo' => 'logo.png',
],
"orderIdentifier" => "1",
"orderManagementURL" => "https://demo-store.test/sales/order/view",
'orderNumber' => '#000000001',
"orderType" => "ecommerce",
"orderTypeIdentifier" => "order.com.pkpass.unit-test",
'payment' => [
'summaryItems' => [
[
'label' => 'Shipping & Handling',
'value' => [
'amount' => '5.00',
'currency' => 'USD',
]
],
],
'total' => [
'amount' => '36.39',
'currency' => 'USB',
],
'status' => 'paid'
],
"status" => "open",
"updatedAt" => "2024-02-01T19:45:50+00:00",
'customer' => [
'emailAddress' => 'roni_cost@example.com',
'familyName' => 'Veronica',
'givenName' => 'Costello',
],
'lineItems' => [
[
'image' => 'ws03-xs-red.jpg',
'price' => [
'amount' => '31.39',
'currency' => 'USD',
],
'quantity' => 1,
'title' => 'Iris Workout Top',
'sku' => 'WS03-XS-Red',
],
],
"schemaVersion" => 1,
]);
$pass->addFile(__DIR__ . '/fixtures/order/logo.png');
$pass->addFile(__DIR__ . '/fixtures/order/ws03-xs-red.jpg');
$value = $pass->create();
$this->validateOrder($value, [
'logo.png',
'ws03-xs-red.jpg',
'manifest.json',
'order.json',
'signature',
]);
}
}
Binary file added tests/fixtures/order/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures/order/ws03-xs-red.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading