Releases: billforward/bf-php
Curl Error Exposition
In the event of a failed request: attempt using using curl_error() to dig up a bit more information.
Subscription Revive
The SDK has had Subscription revive support for a while, but the feature was scoped to just uncancelling in-period subscription cancellations.
As of yesterday's Sandbox release, Subscription revive supports resurrection of Failed and out-of-period subscriptions. The response object is slightly different than before, so the PHP SDK has been updated to support this.
We now unserialize in a typed manner the executionInfo field upon Subscription. It is usually absent, but cases like reviving an out-of-period subscription will result in the production of Invoices. This field gives some information about the newly-generated invoices, and whether execution was performed upon them.
$sub = Bf_Subscription::shamWithID('SUB-1F565848-DB59-4CF0-861F-34BC474E');
var_export($sub->revive());
// outputs:
array (
0 =>
Bf_Subscription::__set_state(array(
'@type' => 'subscription',
'created' => '2016-07-28T11:29:07Z',
'changedBy' => 'CF0CBF36-922B-403F-AA95-22BAD9960BF3',
'updated' => '2016-07-28T11:29:07Z',
'metadata' =>
Bf_MetadataJson::__set_state(array(
)),
'id' => 'SUB-1F565848-DB59-4CF0-861F-34BC474E',
'versionID' => 'SUB-943D2114-FA61-4185-9771-93DBB968',
'accountID' => 'ACC-71F4DCB6-EBC5-4498-9FC1-3FA7E19C',
'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
'productID' => 'PRO-8FCF4545-B5F8-4DCF-AF35-02B4ABED',
'productRatePlanID' => 'PRP-E1B4973A-23DC-45A9-B196-345F61D5',
'name' => 'Test subscription',
'description' => 'Test plan',
'type' => 'Subscription',
'state' => 'AwaitingPayment',
'currentPeriodStart' => '2016-07-28T11:29:07Z',
'currentPeriodEnd' => '2016-08-28T11:29:07Z',
'contractStart' => '2016-04-27T16:56:13Z',
'initialPeriodStart' => '2016-07-28T11:29:07Z',
'successfulPeriods' => 0,
'totalPeriods' => 1,
'dunning' => false,
'managedBy' => 'BillForward',
'versionStart' => '2016-07-28T11:29:07Z',
'versionNumber' => 2,
'creditEnabled' => true,
'aggregateAllSubscriptionsOnAccount' => false,
'failedPaymentBehaviour' => 'None',
'pricingComponentValues' =>
array (
0 =>
Bf_PricingComponentValue::__set_state(array(
'pricingComponentID' => 'PCO-EADB4E2D-E286-427C-A3ED-9F628F94',
'pricingComponentName' => 'Account fee',
'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
'value' => 1,
'inferredFromDefault' => true,
)),
),
'paymentMethodSubscriptionLinks' =>
array (
),
'fixedTerms' =>
array (
),
'executionInfo' =>
Bf_ExecutionResponse::__set_state(array(
'invoiceID' => 'INV-25792074-EFDD-46FC-81E9-CB514B3E',
'invoiceType' => 'FinalArrears',
'currency' => 'USD',
'outstandingAmount' => 0,
'executions' =>
array (
),
)),
'currentTime' => '2016-07-28T11:29:07Z',
'timeOffset' => 0,
'paymentTerms' => 0,
)),
)More accurately unserialize "add charge to Invoice/Subscription" responses
The upstream version of the BillForward API will return to requests for "add charge to Invoice/Subscription" a response typed as Bf_AddChargeResponse.
Version 15 of the BF PHP SDK assumed that the response for:
Bf_Subscription->charge()Bf_Invoice->charge()
Would be an array of Bf_SubscriptionCharge.
Version 16 of the BF PHP SDK will assume that the response for:
Bf_Subscription->charge()Bf_Invoice->charge()
will be a Bf_AddChargeResponse.
Both versions of the BF PHP SDK will happily unserialize responses from either version of BIllForward (no exception will occur). The response would be incorrectly-typed, but as these are just associative arrays, it is still possible to access arbitrary properties of the entity as usual.
Timer Amendments
Support for timer amendments
<?php
$timerAmendment = $sub->setupTimer(
array(
'duration' => 1,
'period' => 'Minutes',
'direction' => 'Before',
'event' => 'PeriodEnd'
)
);
var_export($timerAmendment);
// output example:
Bf_TimerAmendment::__set_state(array(
'@type' => 'TimerAmendment',
'created' => '2016-06-29T12:44:36Z',
'changedBy' => 'CF0CBF36-922B-403F-AA95-22BAD9960BF3',
'updated' => '2016-06-29T12:44:36Z',
'id' => '209C846D-859C-46DF-8982-1DB0BF28C1B9',
'organizationID' => 'ORG-BE199668-332C-4EFD-B05D-D705142C',
'subscriptionID' => 'SUB-E080141E-CDF3-4747-9562-6739E947',
'actioningTime' => '2016-06-29T12:46:09Z',
'state' => 'Pending',
'deleted' => false,
'entityID' => 'SUB-E080141E-CDF3-4747-9562-6739E947',
'entity' => 'Subscription',
'offsetDuration' => 1,
'offsetPeriod' => 'Minutes',
'offsetDirection' => 'Before',
'originalEventDate' => '2016-06-29T12:47:09Z',
'timerEvent' => 'PeriodEnd',
))You will receive a notification like this:
{
"@type": "notification",
"created": "2016-06-29T12:34:11Z",
"id": "45A099B81DC9F60C8E398AFF55E7A3CDBILL",
"domain": "Amendment",
"action": "Updated",
"organizationID": "ORG-BE199668-332C-4EFD-B05D-D705142C",
"entityID": "531DA203-43D6-4020-8887-AEA1D55C411B",
"entity": "{\"@type\":\"TimerAmendment\",\"created\":\"2016-06-29T12:33:43Z\",\"changedBy\":\"System\",\"updated\":\"2016-06-29T12:34:10Z\",\"id\":\"531DA203-43D6-4020-8887-AEA1D55C411B\",\"organizationID\":\"ORG-BE199668-332C-4EFD-B05D-D705142C\",\"subscriptionID\":\"SUB-E080141E-CDF3-4747-9562-6739E947\",\"amendmentType\":\"Timer\",\"actioningTime\":\"2016-06-29T12:34:09Z\",\"actionedTime\":\"2016-06-29T12:34:10Z\",\"state\":\"Succeeded\",\"deleted\":false,\"entityID\":\"SUB-E080141E-CDF3-4747-9562-6739E947\",\"entity\":\"Subscription\",\"offsetDuration\":1,\"offsetPeriod\":\"Minutes\",\"offsetDirection\":\"Before\",\"originalEventDate\":\"2016-06-29T12:35:09Z\",\"timerEvent\":\"PeriodEnd\"}",
"changes": "{\"auditFieldChanges\":[{\"attributeName\":\"actionedTime\",\"newValue\":\"2016-06-29T12:34:10Z\"},{\"attributeName\":\"internalState\",\"previousValue\":\"Pending\",\"newValue\":\"Succeeded\"},{\"attributeName\":\"state\",\"previousValue\":\"Pending\",\"newValue\":\"Succeeded\"}]}"
}Hotfix for `v15.2016.113.1`'s `makeUTCTimeFromBillForwardDate()`
v15.2016.113.1 refactored makeUTCTimeFromBillForwardDate() to move it into a utility class.
However this refactor did not correctly pass args through — meaning that makeUTCTimeFromBillForwardDate() would always use NULL as its date (i.e. "now").
This hotfix corrects that.
Revert to classic `Bf_Subscription->changeValues()`
API support is not yet released for the new "batch changes values" endpoint, so I am rolling back to the classic implementation.
Fix for v14's unserialization of `Bf_MigrationResponse`
Bug fix
v14 enforced that metadata field upon Bf_Subscription needs to be an associative array.
The current API does not successfully post-process the JSON string metadata upon Subscription entities nested within a MigrationResponse.
In this version of the PHP SDK: we compensate for metadata fields that escape post-processing.
Refactor
Since a lot of "utility" methods were living on Bf_BillingEntity, I have now introduced a real utility class (Bf_Util) for utility methods. It provides:
Bf_Util::jsonStrToAssociativeArray($jsonStr)Bf_Util::associativeArrayToJsonStr($arr, $optionsBitMask = 0)Bf_Util::makeBillForwardDate($time)Bf_Util::makeUTCTimeFromBillForwardDate($date)
Bf_BillingEntity::getJson() and Bf_BillingEntity::printJson() now call out to Bf_Util::associativeArrayToJsonStr($arr, $optionsBitMask = 0). This means they now enjoy the exact same serialization code as does BillForwardClient.
Compatibility
Bf_BillingEntity::getJson() and Bf_BillingEntity::printJson() now support PHP 5.3 (because they now recruit JSON_PRETTY_PRINT only if available).
JSON encoding of billing entities will now use PHP 5.4's JSON_UNESCAPED_UNICODE option if available, but fallback to devilan's polyfill otherwise.
Deprecation
The following utility functions still exist upon Bf_BillingEntity, but forward the call to Bf_Util. We recommend switching to Bf_Util if you are relying upon these functions from Bf_BillingEntity presently:
Bf_BillingEntity::makeBillForwardDate($time)Bf_BillingEntity::makeUTCTimeFromBillForwardDate($date)
Any breaking changes?
No breaking changes are expected. All unit tests continue to pass.
This is a major version release simply because it significantly touches important parts of unserialization — and therefore carries enough risk that it merits a retest of the use cases in your BillForward integration.
Fix for reflection
Use of protected static function callFunctionAbstractAndPageThrough() is now fixed: it had failed due to the renaming of an argument upon which reflection relied.
Semantic errors
Features
Added to Bf_APIException the following methods:
getHttpCode()
Added to Bf_NoAPIResponseException (extends Bf_APIException) the following methods:
getRawResponse()
Bf_APIErrorResponseException (extends Bf_APIException) the following methods:
getBFErrorType()getBFErrorMessage()getBFErrorParameters()- usually NULL, but in the case of
ValidationErrorat least: yields a list of which fields upon your entity suffered from validation problems.
- usually NULL, but in the case of
getBFErrorMessage()getRawResponse()getParsedResponse()
Example usage
<?php
try {
Bf_Account::create(new Bf_Account(array(
'profile' => new Bf_Profile(array(
'email' => 'notemail'
))
)));
} catch(Bf_APIErrorResponseException $err) {
echo "Exception message:\n";
echo $err->getMessage();
echo "\n\nHTTP code:\n";
echo $err->getHttpCode();
echo "\n\nRaw response:\n";
echo $err->getRawResponse();
echo "\n\nParsed response:\n";
var_export($err->getParsedResponse());
echo "\n\nBF Error Type:\n";
echo $err->getBFErrorType();
echo "\n\nBF Error Message:\n";
echo $err->getBFErrorMessage();
echo "\n\nBF Error Parameters:\n";
var_export($err->getBFErrorParameters());
echo "\n";
}Output:
Exception message:
====
Received error from API.
Error code: <400>
Error type: <ValidationError>
Error message: <Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address
>.
====
HTTP code:
400
Raw response:
{
"errorType" : "ValidationError",
"errorMessage" : "Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address\n",
"errorCode" : 0,
"errorParameters" : [ "email" ]
}
Parsed response:
array (
'errorType' => 'ValidationError',
'errorMessage' => 'Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address
',
'errorCode' => 0,
'errorParameters' =>
array (
0 => 'email',
),
)
BF Error Type:
ValidationError
BF Error Message:
Validation Error - Entity: com.billforce.billing.shared.profiles.Profile Field: email Value: notemail Message: not a well-formed email address
BF Error Parameters:
array (
0 => 'email',
)
UTF-8 support, query params on POST/PUT/DELETE requests, preflight potentially removed on GETs, batch features
Refactor
- Substantially refactored the
BillForwardClient- Reduced code duplication
- Undid the conflation of "query params" with "payload"; you can now have both
- All code paths involved in
GET/POST/PUT/DELETEnow pass at least an emptyqueryParamsobject. DELETEsupports a JSON payload. Currently unused by the BillForward backend.
Bug fixes
- Empty
POST/PUT/DELETEpayloads are now interpreted as JSON{}instead of[] metadataentity uponAccount,Product,ProductRatePlanandSubscriptionis now interpreted as JSON{}instead of[]- Replaced fake UTF-8 support with actual end-to-end UTF-8 support.
- BillForward backend uses MySQL
utf8charset — so in practice only 3-byte UTF-8 can be supported.
- BillForward backend uses MySQL
Performance
- For
GETrequests (or any request that happens to have no payload): I have moved authentication into query params instead of the authentication header. I believe this way I can avoid anOPTIONSpreflight.
Features
POST/PUT/DELETEcan now have query params- Enables use of the new
void_chargesquery param onDELETE /invoices/{invoice-ID}
- Enables use of the new
- Initial support for batch endpoints (not final, but it's reasonably likely it will stay something like this)
- Add charges
- Add pricing component values
Tests
- All unit tests passing on latest version of API
- Honors newer constraints on name uniqueness
- Honors new constraint on profile updates
- Removes test upon roles of Account, because these are no longer exposed