Skip to content

Commit 18175dc

Browse files
authored
Merge pull request #137 from cjnewbs/feature/add_order_support
2 parents 06ce23c + 8a06375 commit 18175dc

File tree

5 files changed

+221
-4
lines changed

5 files changed

+221
-4
lines changed

src/FinanceOrder.php

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php declare(strict_types=1);
2+
3+
/**
4+
* Copyright (c), Includable.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to
8+
* deal in the Software without restriction, including without limitation the
9+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10+
* sell copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*/
16+
17+
namespace PKPass;
18+
19+
use ZipArchive;
20+
21+
class FinanceOrder extends PKPass
22+
{
23+
const FILE_TYPE = 'order';
24+
const FILE_EXT = 'order';
25+
const MIME_TYPE = 'application/vnd.apple.finance.order';
26+
const PAYLOAD_FILE = 'order.json';
27+
const HASH_ALGO = 'sha256';
28+
29+
/**
30+
* Sub-function of create()
31+
* This function creates the hashes for the files and adds them into a json string.
32+
*
33+
* @throws PKPassException
34+
*/
35+
protected function createManifest()
36+
{
37+
// Creates SHA hashes for all files in package
38+
$sha = [];
39+
$sha[self::PAYLOAD_FILE] = hash(self::HASH_ALGO, $this->json);
40+
41+
// Creates SHA hashes for string files in each project.
42+
foreach ($this->locales as $language => $strings) {
43+
$sha[$language . '.lproj/' . self::FILE_TYPE . '.strings'] = hash(self::HASH_ALGO, $strings);
44+
}
45+
46+
foreach ($this->files as $name => $path) {
47+
$sha[$name] = hash(self::HASH_ALGO, file_get_contents($path));
48+
}
49+
50+
foreach ($this->remote_file_urls as $name => $url) {
51+
$sha[$name] = hash(self::HASH_ALGO, file_get_contents($url));
52+
}
53+
54+
foreach ($this->files_content as $name => $content) {
55+
$sha[$name] = hash(self::HASH_ALGO, $content);
56+
}
57+
58+
return json_encode((object)$sha);
59+
}
60+
61+
/**
62+
* Creates .pkpass zip archive.
63+
*
64+
* @param string $manifest
65+
* @param string $signature
66+
* @return string
67+
* @throws PKPassException
68+
*/
69+
protected function createZip($manifest, $signature)
70+
{
71+
// Package file in Zip (as .order)
72+
$zip = new ZipArchive();
73+
$filename = tempnam($this->tempPath, self::FILE_TYPE);
74+
if (!$zip->open($filename, ZipArchive::OVERWRITE)) {
75+
throw new PKPassException('Could not open ' . basename($filename) . ' with ZipArchive extension.');
76+
}
77+
$zip->addFromString('signature', $signature);
78+
$zip->addFromString('manifest.json', $manifest);
79+
$zip->addFromString(self::PAYLOAD_FILE, $this->json);
80+
81+
// Add translation dictionary
82+
foreach ($this->locales as $language => $strings) {
83+
if (!$zip->addEmptyDir($language . '.lproj')) {
84+
throw new PKPassException('Could not create ' . $language . '.lproj folder in zip archive.');
85+
}
86+
$zip->addFromString($language . '.lproj/' . self::FILE_TYPE . '.strings', $strings);
87+
}
88+
89+
foreach ($this->files as $name => $path) {
90+
$zip->addFile($path, $name);
91+
}
92+
93+
foreach ($this->remote_file_urls as $name => $url) {
94+
$download_file = file_get_contents($url);
95+
$zip->addFromString($name, $download_file);
96+
}
97+
98+
foreach ($this->files_content as $name => $content) {
99+
$zip->addFromString($name, $content);
100+
}
101+
102+
$zip->close();
103+
104+
if (!file_exists($filename) || filesize($filename) < 1) {
105+
@unlink($filename);
106+
throw new PKPassException('Error while creating order.order. Check your ZIP extension.');
107+
}
108+
109+
$content = file_get_contents($filename);
110+
unlink($filename);
111+
112+
return $content;
113+
}
114+
}

src/PKPass.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
*/
2424
class PKPass
2525
{
26+
const FILE_TYPE = 'pass';
27+
const FILE_EXT = 'pkpass';
28+
const MIME_TYPE = 'application/vnd.apple.pkpass';
2629
/**
2730
* Holds the path to the certificate.
2831
* @var string
@@ -55,7 +58,7 @@ class PKPass
5558

5659
/**
5760
* Holds the JSON payload.
58-
* @var object|array
61+
* @var string
5962
*/
6063
protected $json;
6164

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

313316
// Output pass
314317
header('Content-Description: File Transfer');
315-
header('Content-Type: application/vnd.apple.pkpass');
318+
header('Content-Type: ' . self::MIME_TYPE);
316319
header('Content-Disposition: attachment; filename="' . $this->getName() . '"');
317320
header('Content-Transfer-Encoding: binary');
318321
header('Connection: Keep-Alive');
@@ -332,9 +335,9 @@ public function create($output = false)
332335
*/
333336
public function getName()
334337
{
335-
$name = $this->name ?: 'pass';
338+
$name = $this->name ?: self::FILE_TYPE;
336339
if (!strstr($name, '.')) {
337-
$name .= '.pkpass';
340+
$name .= '.' . self::FILE_EXT;
338341
}
339342

340343
return $name;

tests/FinanceOrderTest.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php declare(strict_types=1);
2+
3+
use PHPUnit\Framework\TestCase;
4+
use PKPass\FinanceOrder;
5+
6+
final class FinanceOrderTest extends TestCase
7+
{
8+
private function validateOrder($orer, $expected_files = [])
9+
{
10+
// basic string validation
11+
$this->assertIsString($orer);
12+
$this->assertGreaterThan(100, strlen($orer));
13+
$this->assertStringContainsString('logo.png', $orer);
14+
$this->assertStringContainsString('ws03-xs-red.jpg', $orer);
15+
$this->assertStringContainsString('manifest.json', $orer);
16+
17+
// try to read the ZIP file
18+
$temp_name = tempnam(sys_get_temp_dir(), 'pkpass');
19+
file_put_contents($temp_name, $orer);
20+
$zip = new ZipArchive();
21+
$res = $zip->open($temp_name);
22+
$this->assertTrue($res, 'Invalid ZIP file.');
23+
$this->assertEquals(count($expected_files), $zip->numFiles);
24+
25+
// extract zip to temp dir
26+
$temp_dir = $temp_name . '_dir';
27+
mkdir($temp_dir);
28+
$zip->extractTo($temp_dir);
29+
$zip->close();
30+
echo $temp_dir;
31+
foreach ($expected_files as $file) {
32+
$this->assertFileExists($temp_dir . DIRECTORY_SEPARATOR . $file);
33+
}
34+
}
35+
36+
public function testBasicGeneration()
37+
{
38+
$pass = new FinanceOrder(__DIR__ . '/fixtures/example-certificate.p12', 'password');
39+
$pass->setData([
40+
"createdAt" => "2024-02-01T19:45:50+00:00",
41+
"merchant" => [
42+
"displayName" => "Luma",
43+
"merchantIdentifier" => "merchant.com.pkpass.unit-test",
44+
"url" => "https://demo-store.test/",
45+
'logo' => 'logo.png',
46+
],
47+
"orderIdentifier" => "1",
48+
"orderManagementURL" => "https://demo-store.test/sales/order/view",
49+
'orderNumber' => '#000000001',
50+
"orderType" => "ecommerce",
51+
"orderTypeIdentifier" => "order.com.pkpass.unit-test",
52+
'payment' => [
53+
'summaryItems' => [
54+
[
55+
'label' => 'Shipping & Handling',
56+
'value' => [
57+
'amount' => '5.00',
58+
'currency' => 'USD',
59+
]
60+
],
61+
],
62+
'total' => [
63+
'amount' => '36.39',
64+
'currency' => 'USB',
65+
],
66+
'status' => 'paid'
67+
],
68+
"status" => "open",
69+
"updatedAt" => "2024-02-01T19:45:50+00:00",
70+
'customer' => [
71+
'emailAddress' => 'roni_cost@example.com',
72+
'familyName' => 'Veronica',
73+
'givenName' => 'Costello',
74+
],
75+
'lineItems' => [
76+
[
77+
'image' => 'ws03-xs-red.jpg',
78+
'price' => [
79+
'amount' => '31.39',
80+
'currency' => 'USD',
81+
],
82+
'quantity' => 1,
83+
'title' => 'Iris Workout Top',
84+
'sku' => 'WS03-XS-Red',
85+
],
86+
],
87+
"schemaVersion" => 1,
88+
]);
89+
$pass->addFile(__DIR__ . '/fixtures/order/logo.png');
90+
$pass->addFile(__DIR__ . '/fixtures/order/ws03-xs-red.jpg');
91+
$value = $pass->create();
92+
$this->validateOrder($value, [
93+
'logo.png',
94+
'ws03-xs-red.jpg',
95+
'manifest.json',
96+
'order.json',
97+
'signature',
98+
]);
99+
}
100+
}

tests/fixtures/order/logo.png

3.18 KB
Loading

tests/fixtures/order/ws03-xs-red.jpg

7.27 KB
Loading

0 commit comments

Comments
 (0)