Skip to content

Commit

Permalink
Integrate Sunbit and add its API bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
egeniesse-stripe committed Jul 2, 2024
1 parent 3654e37 commit 4667a69
Show file tree
Hide file tree
Showing 25 changed files with 512 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### Payments
* [Fixed] An issue where the correct card brand was not being displayed for card brand choice in STPPaymentOptionsViewController and STPPaymentContext.
* [Added] Support for Sunbit bindings.

### PaymentSheet
* [Added] Support for Sunbit (Private Beta) with PaymentIntents.

## PaymentSheet
* [Fixed] Fixed an issue where certain cobranded cards showed a generic card icon instead of using the other card brand.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
C1F0577CD62C2569EEEAD1E1 /* StripePayments.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 64F13592DB0CDD803283B035 /* StripePayments.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C5C612D49748599F5A09421D /* StripePayments.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F13592DB0CDD803283B035 /* StripePayments.framework */; };
C9DCB32902B62F3E8F3F9052 /* PaymentExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DF98C4542C942B8BE99AF2AE /* PaymentExampleViewController.m */; };
CAC3A0322C2F1184007BC888 /* SunbitExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC3A0312C2F1184007BC888 /* SunbitExampleViewController.swift */; };
CD7FC52A94895E322DB5B3D9 /* FPXExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4387CA3F60553DB73F05BDC9 /* FPXExampleViewController.m */; };
CF0983C679CA4196DF4DA37D /* PayPalExampleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B0D62F7B5288E04FB14DBFFF /* PayPalExampleViewController.m */; };
D46A05D88420B85C1B275640 /* MyAPIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0AA7CAC14165BD4EFB39E7C5 /* MyAPIClient.m */; };
Expand Down Expand Up @@ -165,6 +166,7 @@
C2E5EE3426E4E27739E36F5A /* PaymentExampleViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PaymentExampleViewController.h; sourceTree = "<group>"; };
C55D540D77E57684BEBD1773 /* StripeFinancialConnections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StripeFinancialConnections.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C7C71C3CD5BC6AB2F0E15AD5 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
CAC3A0312C2F1184007BC888 /* SunbitExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SunbitExampleViewController.swift; sourceTree = "<group>"; };
CD73BDD44CF574D1957C3FFA /* BoletoExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoletoExampleViewController.swift; sourceTree = "<group>"; };
CED93B66CA3A5F01511EE804 /* AlipayExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlipayExampleViewController.swift; sourceTree = "<group>"; };
CF8C12BF1DA08683B9B34E7B /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -308,6 +310,7 @@
EE78B2BB1747FC2994ADC48C /* USBankAccountExampleViewController.swift */,
E3EC1F5B37D8AD554396A3D7 /* WeChatPayExampleViewController.h */,
F1A0CF82AB6742C41BA5C19C /* WeChatPayExampleViewController.m */,
CAC3A0312C2F1184007BC888 /* SunbitExampleViewController.swift */,
);
path = "Non-Card Payment Examples";
sourceTree = "<group>";
Expand Down Expand Up @@ -455,6 +458,7 @@
7347961ABF706602E2915A53 /* USBankAccountConnectionsExampleViewController.swift in Sources */,
07E1B52EA12E4F9789013E8C /* USBankAccountExampleViewController.swift in Sources */,
6BA4B9182BF3E2AF00D1F21D /* MobilePayExampleViewController.swift in Sources */,
CAC3A0322C2F1184007BC888 /* SunbitExampleViewController.swift in Sources */,
616358522C3A1FCD579F2B37 /* WeChatPayExampleViewController.m in Sources */,
975D189CB2E61E7C1871F0DA /* iDEALExampleViewController.m in Sources */,
4331C8C4F8BA76E560F420DF /* main.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 32;
return 33;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Expand Down Expand Up @@ -149,6 +149,9 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N
case 31:
cell.textLabel.text = @"MobilePay";
break;
case 32:
cell.textLabel.text = @"Sunbit";
break;
}
return cell;
}
Expand Down Expand Up @@ -355,6 +358,12 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
viewController = exampleVC;
break;
}
case 32: {
SunbitExampleViewController *exampleVC = [SunbitExampleViewController new];
exampleVC.delegate = self;
viewController = exampleVC;
break;
}
}
[self.navigationController pushViewController:viewController animated:YES];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// SunbitExampleViewController.swift
// Non-Card Payment Examples
//
// Created by Eric Geniesse on 6/27/24.
//

import Foundation
import Stripe
import UIKit

class SunbitExampleViewController: UIViewController {
@objc weak var delegate: ExampleViewControllerDelegate?
var inProgress: Bool = false {
didSet {
navigationController?.navigationBar.isUserInteractionEnabled = !inProgress
payButton.isEnabled = !inProgress
inProgress
? activityIndicatorView.startAnimating() : activityIndicatorView.stopAnimating()
}
}

// UI
lazy var activityIndicatorView = {
return UIActivityIndicatorView(style: .gray)
}()
lazy var payButton: UIButton = {
let button = UIButton(type: .roundedRect)
button.setTitle("Pay with Sunbit", for: .normal)
button.addTarget(self, action: #selector(didTapPayButton), for: .touchUpInside)
return button
}()

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "Sunbit"
[payButton, activityIndicatorView].forEach { subview in
view.addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
}

let constraints = [
payButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
payButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),

activityIndicatorView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
activityIndicatorView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
]
NSLayoutConstraint.activate(constraints)
}

@objc func didTapPayButton() {
guard STPAPIClient.shared.publishableKey != nil else {
delegate?.exampleViewController(
self,
didFinishWithMessage: "Please set a Stripe Publishable Key in Constants.m"
)
return
}
inProgress = true
pay()
}
}

// MARK: - Sunbit
extension SunbitExampleViewController {
@objc func pay() {
// 1. Create an Sunbit PaymentIntent
MyAPIClient.shared().createPaymentIntent(
completion: { (_, clientSecret, error) in
guard let clientSecret = clientSecret else {
self.delegate?.exampleViewController(self, didFinishWithError: error)
return
}

// 2. Confirm the payment and redirect the user to Sunbit
let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
paymentIntentParams.paymentMethodParams = STPPaymentMethodParams(
sunbit: STPPaymentMethodSunbitParams(),
billingDetails: nil,
metadata: nil
)
paymentIntentParams.returnURL = "payments-example://safepay/"

STPPaymentHandler.shared().confirmPayment(
paymentIntentParams,
with: self
) { (status, _, error) in
switch status {
case .canceled:
self.delegate?.exampleViewController(
self,
didFinishWithMessage: "Cancelled"
)
case .failed:
self.delegate?.exampleViewController(self, didFinishWithError: error)
case .succeeded:
self.delegate?.exampleViewController(
self,
didFinishWithMessage: "Payment successfully created."
)
@unknown default:
fatalError()
}
}
},
additionalParameters: "supported_payment_methods=sunbit"
)
}
}

extension SunbitExampleViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1076,4 +1076,44 @@ class PaymentSheetBillingCollectionLPMUITests: PaymentSheetBillingCollectionUITe
var payButton: XCUIElement { app.buttons["Pay €50.99"] }
XCTAssertTrue(payButton.isEnabled)
}

func testLpm_Sunbit_AutomaticFields() throws {
var settings = PaymentSheetTestPlaygroundSettings.defaultValues()
settings.customerMode = .guest
settings.currency = .usd
settings.merchantCountryCode = .US
settings.applePayEnabled = .off
settings.apmsEnabled = .off
settings.linkEnabled = .off
settings.attachDefaults = .off
settings.collectName = .automatic
settings.collectEmail = .automatic
settings.collectPhone = .automatic
settings.collectAddress = .automatic
loadPlayground(
app,
settings
)

checkoutButton.tap()

let cell = try XCTUnwrap(scroll(collectionView: app.collectionViews.firstMatch, toFindCellWithId: "sunbit"))
cell.tap()

XCTAssertFalse(emailField.exists)
XCTAssertFalse(fullNameField.exists)
XCTAssertFalse(phoneField.exists)
XCTAssertFalse(phoneField.exists)
XCTAssertFalse(billingAddressField.exists)
XCTAssertFalse(app.textFields["Country"].exists)
XCTAssertFalse(line1Field.exists)
XCTAssertFalse(line2Field.exists)
XCTAssertFalse(cityField.exists)
XCTAssertFalse(stateField.exists)
XCTAssertFalse(zipField.exists)

// Just check the button is enabled
var payButton: XCUIElement { app.buttons["Pay $50.99"] }
XCTAssertTrue(payButton.isEnabled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,29 @@ class PaymentSheetStandardLPMUITests: PaymentSheetUITestCase {
payButton.tap()
}

func testSunbitPaymentMethod() throws {
var settings = PaymentSheetTestPlaygroundSettings.defaultValues()
settings.currency = .usd
settings.merchantCountryCode = .US
settings.customerMode = .new
settings.apmsEnabled = .off
loadPlayground(app, settings)
app.buttons["Present PaymentSheet"].tap()
let payButton = app.buttons["Pay $50.99"]

// Select Sunbit
guard let sunbit = scroll(collectionView: app.collectionViews.firstMatch, toFindCellWithId: "Sunbit") else {
XCTFail()
return
}
sunbit.tap()

XCTAssertTrue(payButton.isEnabled)

// Attempt payment, should succeed
payButton.tap()
}

func testZipPaymentMethod() throws {
var settings = PaymentSheetTestPlaygroundSettings.defaultValues()
settings.customerMode = .new // new customer
Expand Down
8 changes: 8 additions & 0 deletions Stripe/Stripe.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@
C9E66A22494C02050AE34A9B /* FBSnapshotTestCase+STPViewControllerLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180CF848E3ABF0236C494D8B /* FBSnapshotTestCase+STPViewControllerLoading.swift */; };
CA189278AD606BEAC62D545F /* STPPaymentIntentParamsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF902DC49DD90860BD0E5E80 /* STPPaymentIntentParamsTest.swift */; };
CA4F392070740C56FE2BB461 /* STPStringUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6AE83989B0596F0C111E13 /* STPStringUtilsTest.swift */; };
CAC3A0342C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */; };
CAC3A0382C2F17EF007BC888 /* STPPaymentMethodSunbitParamsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC3A0372C2F17EF007BC888 /* STPPaymentMethodSunbitParamsTests.swift */; };
CB5AADE45B7B7A40514C054B /* StripeApplePay.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 52F8AEC50D4623F80F04A533 /* StripeApplePay.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CBAF9C6F87F746F17495ADC2 /* STPPaymentMethodCashAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DDE50CBC86AD77084C877B6 /* STPPaymentMethodCashAppTests.swift */; };
CBCA59D39B30D869B4FDC04B /* STPE2ETest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C1548BA518F7AC2A9ECF9D5 /* STPE2ETest.swift */; };
Expand Down Expand Up @@ -776,6 +778,8 @@
C8F8FCC84601E4ADC6B7F3CE /* PaymentAnalyticTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAnalyticTest.swift; sourceTree = "<group>"; };
C980D24DDC884FECCE39139F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
CA8B8F540CD05B3DC2C5EEA6 /* STPSetupIntentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPSetupIntentTest.swift; sourceTree = "<group>"; };
CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSunbitTests.swift; sourceTree = "<group>"; };
CAC3A0372C2F17EF007BC888 /* STPPaymentMethodSunbitParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodSunbitParamsTests.swift; sourceTree = "<group>"; };
CBC9D4B0158266B01840AD9A /* STPPaymentMethodOXXOParamsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentMethodOXXOParamsTests.swift; sourceTree = "<group>"; };
CD5AC2BFBC8141F98C00CF9F /* StripeiOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StripeiOS_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
CDDEAB86BE4711841D426F3B /* STPPaymentIntentFunctionalTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPPaymentIntentFunctionalTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1376,6 +1380,8 @@
E68F6B90F3BC61A49570FAF4 /* UINavigationBar+StripeTest.m */,
3B0E131538728BC4802627B1 /* UserDefaults+StripeTest.swift */,
0DB03E83746FE78361831546 /* WalletHeaderViewSnapshotTests.swift */,
CAC3A0332C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift */,
CAC3A0372C2F17EF007BC888 /* STPPaymentMethodSunbitParamsTests.swift */,
);
path = StripeiOSTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1765,6 +1771,7 @@
3EA9D509E59DA65EE4EDF98D /* STPAddressTests.swift in Sources */,
F53E04785DB804EA5C2AAC18 /* STPAddressViewModelTest.swift in Sources */,
0CBBE909CA773D7D45B9AD4C /* STPAnalyticsClientPaymentSheetTest.swift in Sources */,
CAC3A0342C2F176A007BC888 /* STPPaymentMethodSunbitTests.swift in Sources */,
E6F428CFAD64979A8874B00B /* STPAnalyticsClientPaymentsTest.swift in Sources */,
23CF725CFAB2ABED416BF416 /* STPApplePayContextFunctionalTest.swift in Sources */,
6F9525063D76A9F86A10CCBF /* STPApplePayContextFunctionalTestExtras.swift in Sources */,
Expand Down Expand Up @@ -1794,6 +1801,7 @@
EEBA9A95E8057A06E5E7C103 /* STPCardNumberInputTextFieldSnapshotTests.swift in Sources */,
1A058C42C4703458CA1CA522 /* STPCardNumberInputTextFieldValidatorTests.swift in Sources */,
D53C04A27B6B8EFB70E236A7 /* STPCardParamsTest.swift in Sources */,
CAC3A0382C2F17EF007BC888 /* STPPaymentMethodSunbitParamsTests.swift in Sources */,
D15160C0F0763078DBB434E4 /* STPCardTest.swift in Sources */,
1E8D8E2494062262A332879C /* STPCardValidatorTest.swift in Sources */,
A781FB0F586B26655FAEC3C0 /* STPCertTest.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Stripe/StripeiOS/Source/STPPaymentMethod+BasicUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extension STPPaymentMethod: STPPaymentOption {
.przelewy24, .bancontact,
.OXXO, .sofort, .grabPay, .netBanking, .UPI, .afterpayClearpay, .blik,
.weChatPay, .boleto, .klarna, .affirm, .cashApp, .paynow, .zip, .revolutPay, .amazonPay,
.alma, .mobilePay, .konbini, .promptPay, .swish, .twint, .multibanco,
.alma, .mobilePay, .konbini, .promptPay, .swish, .twint, .multibanco, .sunbit,
.unknown:
return false

Expand Down
3 changes: 2 additions & 1 deletion Stripe/StripeiOS/Source/STPPaymentMethodParams+BasicUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ extension STPPaymentMethodParams: STPPaymentOption {
case .alipay, .AUBECSDebit, .bacsDebit, .SEPADebit, .iDEAL, .FPX, .cardPresent, .giropay,
.grabPay, .EPS, .przelewy24, .bancontact, .netBanking, .OXXO, .payPal, .sofort, .UPI,
.afterpayClearpay, .blik, .weChatPay, .boleto, .klarna, .affirm, .cashApp, .paynow,
.zip, .revolutPay, .amazonPay, .alma, .mobilePay, .konbini, .promptPay, .swish, .twint, .multibanco,
.zip, .revolutPay, .amazonPay, .alma, .mobilePay, .konbini, .promptPay, .swish, .twint,
.multibanco, .sunbit,
.unknown:
return false
@unknown default:
Expand Down
Loading

0 comments on commit 4667a69

Please sign in to comment.