├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── INSTALL.md
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── iPaymentButton
│ ├── DemoApplePay.swift
│ ├── ExampleApp.png
│ └── iPaymentButton.swift
└── Tests
├── LinuxMain.swift
└── iPaymentButtonTests
├── XCTestManifests.swift
└── iPaymentButtonTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | [*<<< Back to README*](https://github.com/benjaminsage/iPages)
2 |
3 |
4 | # Instructions
5 |
6 | ## **(1/3)** Open XCode. Go to [File > Swift Packges > Add Package Dependency...]
7 | 
8 |
9 | ## (2/3) Input https://github.com/benjaminsage/iPages.git & click Next.
10 | 
11 |
12 | ## (3/3) Select Version: Up to Next Major & click finish.
13 | 
14 |
15 |
16 |
17 | [*<<< Back to README*](https://github.com/benjaminsage/iPages)
18 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "iGraphics",
6 | "repositoryURL": "https://github.com/benjaminsage/iGraphics.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "eaeec808bbbac6df127fc87d6255cc8cfb91ec15",
10 | "version": "0.0.5"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "iPaymentButton",
8 | defaultLocalization: "en",
9 | platforms: [
10 | .iOS(.v13)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, and make them visible to other packages.
14 | .library(
15 | name: "iPaymentButton",
16 | targets: ["iPaymentButton"]),
17 | ],
18 | dependencies: [
19 | .package(url: "https://github.com/benjaminsage/iGraphics.git", from: "0.0.1")
20 | // Dependencies declare other packages that this package depends on.
21 | // .package(url: /* package url */, from: "1.0.0"),
22 | ],
23 | targets: [
24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
25 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
26 | .target(
27 | name: "iPaymentButton",
28 | dependencies: ["iGraphics"],
29 | resources: [.process("ExampleApp.png")]),
30 | .testTarget(
31 | name: "iPaymentButtonTests",
32 | dependencies: ["iPaymentButton"]),
33 | ]
34 | )
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
iPaymentButton 💵
2 | Quickly implement & easily customize the Apple Pay button. 🤑
3 |
4 | Get Started |
5 | Examples |
6 | Customize |
7 | Install |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Get Started
16 |
17 | 1. [Install](https://github.com/benjaminsage/iPaymentButton/blob/main/INSTALL.md) `iPages`
18 |
19 | 2. Add `iPaymentButton` to your project
20 | ```swift
21 | import SwiftUI
22 | import iPaymentButton
23 |
24 | struct ContentView: View {
25 | var body: some View {
26 | iPaymentButton(action: {
27 | // Add your custom payment code here
28 | })
29 | }
30 | }
31 | ```
32 |
33 | 3. Customize your `iPages`
34 |
35 |
36 | ## Examples
37 | ### Starter
38 |
39 |
40 | Use our built-in `applePayDemo()` to demo a purchase experience.
41 |
42 | ```swift
43 | import SwiftUI
44 | import iPaymentButton
45 | import iGraphics
46 |
47 | struct ContentView: View {
48 | var body: some View {
49 | iGraphicsBox().stack(3)
50 |
51 | iPaymentButton(action: {
52 | iPaymentButton.applePayDemo()
53 | // Add your custom payment code here
54 | })
55 | }
56 | }
57 | ```
58 |
59 |
60 | ### Modern
61 |
62 | Change the button type and style.
63 |
64 | ```swift
65 | import SwiftUI
66 | import iPaymentButton
67 | import iGraphics
68 |
69 | struct ContentView: View {
70 | var body: some View {
71 | iGraphicsView(.first)
72 |
73 | iPaymentButton(type: .support, style: .whiteOutline, action: {
74 | iPaymentButton.applePayDemo()
75 | // Add your custom payment code here
76 | })
77 | }
78 | }
79 | ```
80 |
81 |
82 | ## Customize
83 | `iPaymentButton` has one required parameter: an action to execute on button tap. `iPaymentButton` supports several custom initializers and one custom modifier.
84 |
85 | **Example**: Change the text, style and corner radius with the following code block:
86 | ```swift
87 | iPaymentButton(type: .support, style: .whiteOutline, action: { } )
88 | .cornerRadius(25)
89 | ```
90 |
91 | Use our exhaustive input list to customize your views.
92 |
93 | Modifier or Initializer | Description
94 | --- | ---
95 | `type: PKPaymentButtonType = .buy` | The text written on the button. 🆒
96 | `type: PKPaymentButtonStyle = .black` | The color that the button should be. 🎨
97 | `action: @escaping () -> Void` | The action to be performed when the user taps the button. 🎬▶️
98 | `.cornerRadius(radius: CGFloat) -> iPaymentButton` | Modifies the corner radius of the payment button ⬛️⚫️. To remove the rounded courners, set this value to 0.0 0️⃣👌. The default value is set to 4.0 🍀.
99 |
100 |
101 | ## Install
102 | Use the Swift package manager to install. Find instructions [here](https://github.com/benjaminsage/iPaymentButton/blob/main/INSTALL.md)😀
103 |
104 |
--------------------------------------------------------------------------------
/Sources/iPaymentButton/DemoApplePay.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DemoApplePay.swift
3 | // DummyTest
4 | //
5 | // Created by Kalil Fine on 10/5/20.
6 | //
7 |
8 | //
9 | // ApplePay.swift
10 | // ConsumerApp
11 | //
12 | // Created by Benjamin Sage on 7/10/20.
13 | // Copyright © 2020 DeuceDrone. All rights reserved.
14 | //
15 |
16 | import UIKit
17 | import PassKit
18 | import SwiftUI
19 | import iGraphics
20 |
21 | public extension iPaymentButton {
22 |
23 | @available(iOS 13.0.0, *)
24 | public struct DemoDonation: View {
25 | public var body: some View {
26 | VStack {
27 | CardsApp()
28 | iPaymentButton(type: .donate, style: .whiteOutline, action: {
29 | applePayDemo()
30 | })
31 | }
32 | }
33 | }
34 |
35 | @available(iOS 13.0.0, *)
36 | public struct DemoPrimary: View {
37 | public var body: some View {
38 | VStack {
39 | ShoppingApp()
40 | iPaymentButton(action: {
41 | applePayDemo()
42 | })
43 | }
44 | }
45 | }
46 |
47 |
48 | @available(iOS 14.0.0, *)
49 | public struct DemoGraphics: View {
50 | public var body: some View {
51 | VStack {
52 | MediaApp()
53 | iPaymentButton(type: .support, style: .whiteOutline, action: {
54 | applePayDemo()
55 | })
56 | }
57 | }
58 | }
59 |
60 |
61 | @available(iOS 13.0.0, *)
62 | public struct ShoppingApp: View {
63 | public var body: some View {
64 | iGraphicsBox()
65 | .stack(3)
66 | }
67 | }
68 |
69 | @available(iOS 13.0.0, *)
70 | public struct CardsApp: View {
71 | public var body: some View {
72 | iGraphicsBox()
73 | .stack([.card, .caption])
74 | }
75 | }
76 |
77 | @available(iOS 13.0.0, *)
78 | public struct MediaApp: View {
79 | public var body: some View {
80 | iGraphicsView(.first)
81 | }
82 | }
83 |
84 |
85 | @available(iOS 11.0, *)
86 | public static func applePayDemo() {
87 | ExampleApplePayPopup().pay()
88 | }
89 | }
90 |
91 | public typealias PaymentCompletionHandler = (Bool) -> Void
92 |
93 | @available(iOS 11.0, *)
94 | public class ExampleApplePayPopup: NSObject {
95 | public override init() {}
96 |
97 | var paymentController: PKPaymentAuthorizationController?
98 | var paymentSummaryItems = [PKPaymentSummaryItem]()
99 | var paymentStatus = PKPaymentAuthorizationStatus.failure
100 | var completionHandler: PaymentCompletionHandler!
101 |
102 | public static let supportedNetworks: [PKPaymentNetwork] = [
103 | .amex,
104 | .discover,
105 | .masterCard,
106 | .visa
107 | ]
108 |
109 | public class func applePayStatus() -> (canMakePayments: Bool, canSetupCards: Bool) {
110 | return (PKPaymentAuthorizationController.canMakePayments(),
111 | PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks))
112 | }
113 |
114 | private func demoCompletion(_: Bool) {}
115 |
116 | // public func startPayment(completion: @escaping PaymentCompletionHandler) {
117 | public func pay() {
118 | completionHandler = demoCompletion
119 |
120 | let subtotal = PKPaymentSummaryItem(label: "Subtotal", amount: 87.24, type: .final)
121 | let serviceFee = PKPaymentSummaryItem(label: "Service Fee", amount: 4.02, type: .final)
122 | let tax = PKPaymentSummaryItem(label: "Tax", amount: 8.04, type: .final)
123 | let total = PKPaymentSummaryItem(label: "Demo Corp", amount: 99.30, type: .final)
124 | paymentSummaryItems = [subtotal, serviceFee, tax, total]
125 |
126 | // Create our payment request
127 | let paymentRequest = PKPaymentRequest()
128 | paymentRequest.paymentSummaryItems = paymentSummaryItems
129 | paymentRequest.merchantIdentifier = "merchant.com.demo"
130 | paymentRequest.merchantCapabilities = .capability3DS
131 | paymentRequest.countryCode = "US"
132 | paymentRequest.currencyCode = "USD"
133 | paymentRequest.requiredShippingContactFields = [.postalAddress, .phoneNumber]
134 | paymentRequest.supportedNetworks = ExampleApplePayPopup.supportedNetworks
135 |
136 | // Display our payment request
137 | paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest)
138 | paymentController?.delegate = self
139 | paymentController?.present(completion: { (presented: Bool) in
140 | if presented {
141 | // debugPrint("Presented payment controller")
142 | } else {
143 | // debugPrint("Failed to present payment controller")
144 | self.completionHandler(false)
145 | }
146 | })
147 |
148 | }
149 | }
150 |
151 | /*
152 | PKPaymentAuthorizationControllerDelegate conformance.
153 | */
154 | @available(iOS 11.0, *)
155 | extension ExampleApplePayPopup: PKPaymentAuthorizationControllerDelegate {
156 |
157 | public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
158 |
159 | // Perform some very basic validation on the provided contact information
160 | let errors = [Error]()
161 | let status = PKPaymentAuthorizationStatus.success
162 | // if payment.shippingContact?.postalAddress?.isoCountryCode != "US" {
163 | // let pickupError = PKPaymentRequest.paymentShippingAddressUnserviceableError(withLocalizedDescription: "Sample App only picks up in the United States")
164 | // let countryError = PKPaymentRequest.paymentShippingAddressInvalidError(withKey: CNPostalAddressCountryKey, localizedDescription: "Invalid country")
165 | // errors.append(pickupError)
166 | // errors.append(countryError)
167 | // status = .failure
168 | // } else {
169 | // // Here you would send the payment token to your server or payment provider to process
170 | // // Once processed, return an appropriate status in the completion handler (success, failure, etc)
171 | // }
172 |
173 | self.paymentStatus = status
174 | print(payment)
175 | completion(PKPaymentAuthorizationResult(status: status, errors: errors))
176 |
177 |
178 | }
179 |
180 | public func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
181 | controller.dismiss {
182 | print("payment controller did finish")
183 | // We are responsible for dismissing the payment sheet once it has finished
184 | DispatchQueue.main.async {
185 | if self.paymentStatus == .success {
186 | self.completionHandler!(true)
187 | } else {
188 | self.completionHandler!(false)
189 | }
190 | }
191 |
192 | }
193 | }
194 |
195 | public func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectPaymentMethod paymentMethod: PKPaymentMethod, handler completion: @escaping (PKPaymentRequestPaymentMethodUpdate) -> Void) {
196 | // The didSelectPaymentMethod delegate method allows you to make changes when the user updates their payment card
197 | // Here we're applying a $2 discount when a debit card is selected
198 | guard paymentMethod.type == .debit else {
199 | completion(PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: paymentSummaryItems))
200 | return
201 | }
202 |
203 | // var discountedSummaryItems = paymentSummaryItems
204 | // let discount = PKPaymentSummaryItem(label: "Debit Card Discount", amount: NSDecimalNumber(string: "-2.00"))
205 | // discountedSummaryItems.insert(discount, at: paymentSummaryItems.count - 1)
206 | // if let total = paymentSummaryItems.last {
207 | // total.amount = total.amount.subtracting(NSDecimalNumber(string: "2.00"))
208 | // }
209 | // completion(PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: discountedSummaryItems))
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/Sources/iPaymentButton/ExampleApp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blsage/iPaymentButton/33348c69e980a48e93d1468a010dc4b2c6721b0b/Sources/iPaymentButton/ExampleApp.png
--------------------------------------------------------------------------------
/Sources/iPaymentButton/iPaymentButton.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import PassKit
3 | import SwiftUI
4 |
5 | @available(iOS 13.0, *)
6 | /// A **payment button** 💵 wrapper view around Apple's PassKit `PKPaymentButton` ☑️
7 | /// which allows the button to be drawn and rendered complely using SwiftUI. 🙌
8 | public struct iPaymentButton: View {
9 |
10 | private var type: PKPaymentButtonType
11 | private var style: PKPaymentButtonStyle
12 | private var cornerRadius: CGFloat = 4.0
13 | private var action: () -> Void
14 |
15 | /// Creates a new payment button. 🏗👷♀️💰
16 | /// - Parameters:
17 | /// - type: The **text** written on the button 🆒
18 | /// - style: The **color** that the button should be 🎨
19 | /// - action: The **action** to be performed when the user taps the button 🎬▶️
20 | public init(type: PKPaymentButtonType = .buy,
21 | style: PKPaymentButtonStyle = .black,
22 | action: @escaping () -> Void)
23 | {
24 | self.type = type
25 | self.style = style
26 | self.action = action
27 | }
28 |
29 | public var body: some View {
30 | Button(action: action, label: { EmptyView() } )
31 | .buttonStyle(iPaymentButtonStyle(type: type, style: style, cornerRadius: cornerRadius))
32 | }
33 | }
34 |
35 | public extension iPaymentButton {
36 | /// Modifies the **corner radius** of the payment button. ⬛️⚫️
37 | ///
38 | /// To remove the rounded courners, set this value to 0.0. 0️⃣👌
39 | ///
40 | /// The default value is set to 4.0 🍀4️⃣
41 | /// - Parameter radius: The desired corner radius in points ⬜️⚪️
42 | /// - Returns: A payment button with the desired corner radius 📄
43 | func cornerRadius(_ radius: CGFloat) -> iPaymentButton {
44 | var view = self
45 | view.cornerRadius = radius
46 | return view
47 | }
48 | }
49 |
50 | @available(iOS 13.0.0, *)
51 | fileprivate struct iPaymentButtonStyle: ButtonStyle {
52 | var type: PKPaymentButtonType
53 | var style: PKPaymentButtonStyle
54 | var cornerRadius: CGFloat
55 | func makeBody(configuration: Self.Configuration) -> some View {
56 | return iPaymentButtonHelper(type: type, style: style, cornerRadius: cornerRadius)
57 | }
58 | }
59 |
60 | @available(iOS 13.0.0, *)
61 | fileprivate struct iPaymentButtonHelper: View {
62 | var type: PKPaymentButtonType
63 | var style: PKPaymentButtonStyle
64 | var cornerRadius: CGFloat
65 | var body: some View {
66 | iPaymentButtonRepresentable(type: type, style: style, cornerRadius: cornerRadius)
67 | .frame(minWidth: 100, maxWidth: 400)
68 | .frame(height: 60)
69 | .frame(maxWidth: .infinity)
70 | }
71 | }
72 |
73 | @available(iOS 13.0.0, *)
74 | extension iPaymentButtonHelper {
75 | struct iPaymentButtonRepresentable: UIViewRepresentable {
76 | var type: PKPaymentButtonType
77 | var style: PKPaymentButtonStyle
78 | var cornerRadius: CGFloat
79 |
80 | var button: PKPaymentButton {
81 | let button = PKPaymentButton(paymentButtonType: type, paymentButtonStyle: style)
82 | button.cornerRadius = cornerRadius
83 | return button
84 | }
85 |
86 | func makeUIView(context: Context) -> PKPaymentButton {
87 | return button
88 | }
89 | func updateUIView(_ uiView: PKPaymentButton, context: Context) { }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import iPaymentButtonTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += iPaymentButtonTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/iPaymentButtonTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(iPaymentButtonTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Tests/iPaymentButtonTests/iPaymentButtonTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import iPaymentButton
3 |
4 | final class iPaymentButtonTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | // XCTAssertEqual(iPaymentButton().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------