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

EIT-426: add intro text options to price breakdown #175

Merged
merged 6 commits into from
Sep 6, 2021
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
4 changes: 4 additions & 0 deletions Afterpay.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
157E88D125CBCA49007E54C4 /* Result+Fold.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157E88D025CBCA49007E54C4 /* Result+Fold.swift */; };
15EC67D225E6217F007DFEA8 /* OSLog+Afterpay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15EC67D125E6217F007DFEA8 /* OSLog+Afterpay.swift */; };
15F7DDB725393BD30011EC25 /* CurrencyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15F7DDB625393BD30011EC25 /* CurrencyFormatter.swift */; };
42DA4F9826E0740500204E75 /* IntroText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42DA4F9726E0740500204E75 /* IntroText.swift */; };
550D48152625539900C0B0C6 /* WidgetStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D48142625539900C0B0C6 /* WidgetStatusTests.swift */; };
550D481B26255D8600C0B0C6 /* WidgetEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550D481A26255D8600C0B0C6 /* WidgetEventTests.swift */; };
5519DFAC261D38A8000628FF /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5519DFAB261D38A8000628FF /* README.md */; };
Expand Down Expand Up @@ -80,6 +81,7 @@
15EC67D125E6217F007DFEA8 /* OSLog+Afterpay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Afterpay.swift"; sourceTree = "<group>"; };
15F7DDB625393BD30011EC25 /* CurrencyFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyFormatter.swift; sourceTree = "<group>"; };
15FAC56625DCCEDF00DE7792 /* Afterpay.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Afterpay.podspec; sourceTree = "<group>"; };
42DA4F9726E0740500204E75 /* IntroText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroText.swift; sourceTree = "<group>"; };
550D48142625539900C0B0C6 /* WidgetStatusTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetStatusTests.swift; sourceTree = "<group>"; };
550D481A26255D8600C0B0C6 /* WidgetEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetEventTests.swift; sourceTree = "<group>"; };
5519DFAB261D38A8000628FF /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
Expand Down Expand Up @@ -340,6 +342,7 @@
6615F99A24D14620005036F1 /* SVG.swift */,
557511C226489F090040CC51 /* SVG+Source.swift */,
6672982925357D80001D1C5A /* SVGConfiguration.swift */,
42DA4F9726E0740500204E75 /* IntroText.swift */,
);
path = Resources;
sourceTree = "<group>";
Expand Down Expand Up @@ -569,6 +572,7 @@
66D685B224BD3FB900C7287C /* SwiftUIWrapper.swift in Sources */,
666D334C24A48F5C00FCD464 /* ObjcWrapper.swift in Sources */,
55432830263A61C4005512E4 /* CombineWrapper.swift in Sources */,
42DA4F9826E0740500204E75 /* IntroText.swift in Sources */,
157E88D125CBCA49007E54C4 /* Result+Fold.swift in Sources */,
55A2D307261BB36C00D8E23A /* Money.swift in Sources */,
661D4323257DF1CB00ACCDE1 /* ShippingOption.swift in Sources */,
Expand Down
8 changes: 4 additions & 4 deletions Afterpay.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
"repositoryURL": "https://github.com/drmohundro/SWXMLHash",
"state": {
"branch": null,
"revision": "a4931e5c3bafbedeb1601d3bb76bbe835c6d475a",
"version": "5.0.1"
"revision": "9183170d20857753d4f331b0ca63f73c60764bf3",
"version": "5.0.2"
}
},
{
"package": "TrustKit",
"repositoryURL": "https://github.com/datatheorem/TrustKit",
"state": {
"branch": null,
"revision": "714fd3fcdcada5b107d91bf6caaaefb00f792730",
"version": "1.6.5"
"revision": "3c953558d61fdd9b136d981764e3242bd92b2648",
"version": "1.7.0"
}
}
]
Expand Down
2 changes: 1 addition & 1 deletion Configurations/Afterpay-Shared.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,4 @@ TARGETED_DEVICE_FAMILY = 1,2
// This setting defines the user-visible version of the project. The value corresponds to
// the `CFBundleShortVersionString` key in your app's Info.plist.

MARKETING_VERSION = 3.0.3
MARKETING_VERSION = 3.0.4
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private final class ContentStackViewController: UIViewController, PriceBreakdown
stack.addArrangedSubview(badgeStack)

let priceBreakdown1 = PriceBreakdownView()
priceBreakdown1.introText = AfterpayIntroText.payInTitle
priceBreakdown1.totalAmount = 100
priceBreakdown1.delegate = self
stack.addArrangedSubview(priceBreakdown1)
Expand Down
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This is the recommended integration method.

```
dependencies: [
.package(url: "https://github.com/afterpay/sdk-ios.git", .upToNextMajor(from: "3.0.3"))
.package(url: "https://github.com/afterpay/sdk-ios.git", .upToNextMajor(from: "3.0.4"))
]
```

Expand Down Expand Up @@ -109,7 +109,7 @@ Add the Afterpay SDK as a [git submodule][git-submodule] by navigating to the ro
```
git submodule add https://github.com/afterpay/sdk-ios.git Afterpay
cd Afterpay
git checkout 3.0.3
git checkout 3.0.4
```

#### Project / Workspace Integration
Expand Down Expand Up @@ -174,7 +174,7 @@ dataStore.fetchDataRecords(ofTypes: dataTypes) { records in

The checkout widget displays the consumer's payment schedule, and can be updated as the order total changes. It should be shown if the order value is going to change after the Afterpay Express checkout has finished. For example, the order total may change in response to shipping costs and promo codes. It can also be used to show if there are any barriers to completing the purchase, like if the customer has gone over their Afterpay payment limit.

It can be used in two ways: with a checkout token (from checkout v2) or with a monetary amount (also known as 'tokenless mode').
It can be used in two ways: with a checkout token (from checkout v2) or with a monetary amount (also known as 'tokenless mode').

```swift
// With token:
Expand Down Expand Up @@ -240,7 +240,7 @@ The Afterpay badge is a simple `UIView` that can be scaled to suit the needs of
let badgeView = BadgeView()
```

Below are examples of the badge in each of the color schemes:
Below are examples of the badge in each of the color schemes:
![Black on Mint badge][badge-black-on-mint] ![Mint on Black badge][badge-mint-on-black] ![White on Black badge][badge-white-on-black] ![Black on White badge][badge-black-on-white]

### Payment Button
Expand All @@ -250,7 +250,7 @@ The Afterpay `PaymentButton` is a subclass of `UIButton` that can be scaled to s
Below are examples of the button in each of the color schemes:
| Mint and Black | Black and White |
| -- | -- |
| ![Black on Mint button][button-black-on-mint] | ![White on Black button][button-white-on-black] |
| ![Black on Mint button][button-black-on-mint] | ![White on Black button][button-white-on-black] |
| ![Mint on Black button][button-mint-on-black] | ![Black on White button][button-black-on-white] |

There are also a few other kinds of payment available, with different wording:
Expand Down Expand Up @@ -296,11 +296,25 @@ A total payment amount (represented as a Swift Decimal) must be programatically
let totalAmount = Decimal(string: price) ?? .zero

let priceBreakdownView = PriceBreakdownView()
priceBreakdownView.introText = AfterpayIntroText.payInTitle
priceBreakdownView.totalAmount = totalAmount
```

After setting the total amount the matching breakdown string for the set Afterpay configuration will be displayed.

### Intro Text
Setting `introText` is optional, will default to `or` and must be of type `AfterpayIntroText`.

Can be any of `or`, `orTitle`, `pay`, `payTitle`, `make`, `makeTitle`, `payIn`, `payInTitle`, `in`, `inTitle` or `empty` (no intro text).
Intro text will be rendered lowercase unless using an option suffixed with `Title` in which case title case will be rendered.

```swift
let priceBreakdownView = PriceBreakdownView()
priceBreakdownView.introText = AfterpayIntroText.makeTitle
```

Given the above, the price breakdown text will be rendered `Make 4 interest-free payments of $##.##`

### Examples

When the breakdown component is assigned a total amount that is valid for the merchant account, the component will display 4 instalment amounts.
Expand Down Expand Up @@ -434,7 +448,7 @@ You may also choose to send the desired locale and/or environment data back from

The following examples are in Swift and UIKit. Objective-C and SwiftUI wrappers have not been provided at this time for v2. Please raise an issue if you would like to see them implemented.

> **NOTE:**
> **NOTE:**
> Two requirements must be met in order to use checkout v2 successfully:
> - Configuration must always be set before presentation otherwise you will incur an assertionFailure.
> - When creating a checkout token `popupOriginUrl` must be set to `https://static.afterpay.com`. The SDK’s example merchant server sets the parameter [here](https://github.com/afterpay/sdk-example-server/blob/master/src/routes/checkout.ts#L28). See more at by checking the [api reference][express-checkout]. Failing to do so will cause undefined behavior.
Expand Down Expand Up @@ -554,7 +568,7 @@ WidgetView.init(amount:)

### Widget Options

The widget has appearance options. You can provide these when you initialise the `WidgetView`.
The widget has appearance options. You can provide these when you initialise the `WidgetView`.

Both initialisers take an optional second parameter: a `WidgetView.Style`. The style type contains the appearance options for the widget. At the moment, the only options for `Style` are booleans for the `logo` and the `header`. By default, they are `true`.

Expand All @@ -576,7 +590,7 @@ widgetView.layer.borderColor = UIColor.someOtherColor

### Updating the Widget

The order total will change due to circumstances like promo codes, shipping options, _et cetera_. When the it has changed, you should inform the widget so that it can update what it is displaying.
The order total will change due to circumstances like promo codes, shipping options, _et cetera_. When the it has changed, you should inform the widget so that it can update what it is displaying.

You may send updates to the widget via its `sendUpdate(amount:)` function. The `amount` parameter is the total amount of the order. It must be in the same currency that was sent to `Afterpay.setConfiguration`. The configuration object *must* be set before calling this method, or it will throw.

Expand All @@ -591,12 +605,12 @@ You can also enquire about the current status of the widget. This is an asynchro
(If you wish to be informed when the status has changed, consider setting a `WidgetHandler`)

```swift
widgetView.getStatus { result in
widgetView.getStatus { result in
// handle result
}
```

The `result` returned, if successful, is a `WidgetStatus`. This tells you if the widget is either in a valid or invalid state. `WidgetStatus` is an enum with two cases: `valid` and `invalid`. Each case has associated values appropriate for their circumstances.
The `result` returned, if successful, is a `WidgetStatus`. This tells you if the widget is either in a valid or invalid state. `WidgetStatus` is an enum with two cases: `valid` and `invalid`. Each case has associated values appropriate for their circumstances.

`valid` has the amount of money due today and the payment schedule checksum. The checksum is a unique value representing the payment schedule that must be provided when capturing the order. `invalid` has the error code and error message. The error code and message are optional.

Expand All @@ -614,7 +628,7 @@ final class ExampleWidgetHandler: WidgetHandler {
}

func onChanged(status: WidgetStatus) {
// The widget has had an update.
// The widget has had an update.
}

func onError(errorCode: String?, message: String?) {
Expand All @@ -633,7 +647,7 @@ final class MyViewController: UIViewController {

init() {
// ... snip ...

// Do this some time before displaying the widget. Doesn't have to be in init()
Afterpay.setWidgetHandler(widgetHandler)
}
Expand Down
8 changes: 6 additions & 2 deletions Sources/Afterpay/Model/PriceBreakdown.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ struct PriceBreakdown {
let string: String
let badgePlacement: BadgePlacement

init(totalAmount: Decimal) {
init(
totalAmount: Decimal,
introText: AfterpayIntroText = AfterpayIntroText.or
) {
let configuration = getConfiguration()
let formatter = configuration
.map { CurrencyFormatter(locale: $0.locale, currencyCode: $0.currencyCode) }
Expand All @@ -35,7 +38,8 @@ struct PriceBreakdown {

if let formattedPayment = formattedPayment, inRange {
badgePlacement = .end
string = String(format: Strings.fourPaymentsFormat, formattedPayment)
string = String(format: Strings.fourPaymentsFormat, introText.rawValue, formattedPayment)
.trimmingCharacters(in: .whitespaces)
} else if let formattedMinimum = formattedMinimum, let formattedMaximum = formattedMaximum {
badgePlacement = .start
string = String(format: Strings.availableBetweenFormat, formattedMinimum, formattedMaximum)
Expand Down
23 changes: 23 additions & 0 deletions Sources/Afterpay/Resources/IntroText.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// IntroText.swift
// Afterpay
//
// Created by Scott Antonac on 25/8/21.
// Copyright © 2021 Afterpay. All rights reserved.
//

import Foundation

public enum AfterpayIntroText: String {
case empty = ""
case make = "make"
case makeTitle = "Make"
case pay = "pay"
case payTitle = "Pay"
case `in` = "in"
case inTitle = "In"
case or = "or"
case orTitle = "Or"
case payIn = "pay in"
case payInTitle = "Pay in"
}
huwr marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion Sources/Afterpay/Resources/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum Strings {

static let availableBetweenFormat = "available for orders between %@ - %@"
static let availableUpToFormat = "available for orders up to %@"
static let fourPaymentsFormat = "or 4 interest-free payments of %@ with"
static let fourPaymentsFormat = "%@ 4 interest-free payments of %@ with"

// MARK: - Accessible Strings

Expand Down
8 changes: 7 additions & 1 deletion Sources/Afterpay/Views/PriceBreakdownView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public final class PriceBreakdownView: UIView {
}
}

public var introText: AfterpayIntroText = AfterpayIntroText.or {
didSet {
updateAttributedText()
}
}

public var textColor: UIColor = {
if #available(iOS 13.0, *) {
return .label
Expand Down Expand Up @@ -153,7 +159,7 @@ public final class PriceBreakdownView: UIView {

let space = NSAttributedString(string: " ", attributes: textAttributes)

let priceBreakdown = PriceBreakdown(totalAmount: totalAmount)
let priceBreakdown = PriceBreakdown(totalAmount: totalAmount, introText: introText)
let breakdown = NSAttributedString(string: priceBreakdown.string, attributes: textAttributes)

let badgePlacement = priceBreakdown.badgePlacement
Expand Down